Merge "Revert "Move widget picker tests to Robolectric"" into main
diff --git a/aconfig/launcher.aconfig b/aconfig/launcher.aconfig
index dd78ca4..8682e5d 100644
--- a/aconfig/launcher.aconfig
+++ b/aconfig/launcher.aconfig
@@ -277,3 +277,17 @@
     purpose: PURPOSE_BUGFIX
   }
 }
+
+flag {
+    name: "enabled_folders_in_all_apps"
+    namespace: "launcher"
+    description: "Enables folders in all apps"
+    bug: "341582436"
+}
+
+flag {
+    name: "enable_tiny_taskbar"
+    namespace: "launcher"
+    description: "Enables Taskbar on phones"
+    bug: "341784466"
+}
diff --git a/go/quickstep/src/com/android/quickstep/TaskOverlayFactoryGo.java b/go/quickstep/src/com/android/quickstep/TaskOverlayFactoryGo.java
index 0eb8775..26ca06a 100644
--- a/go/quickstep/src/com/android/quickstep/TaskOverlayFactoryGo.java
+++ b/go/quickstep/src/com/android/quickstep/TaskOverlayFactoryGo.java
@@ -56,7 +56,7 @@
 import com.android.quickstep.util.AssistContentRequester;
 import com.android.quickstep.util.RecentsOrientedState;
 import com.android.quickstep.views.GoOverviewActionsView;
-import com.android.quickstep.views.TaskThumbnailViewDeprecated;
+import com.android.quickstep.views.TaskView.TaskContainer;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.recents.model.ThumbnailData;
 
@@ -101,8 +101,8 @@
     /**
      * Create a new overlay instance for the given View
      */
-    public TaskOverlayGo createOverlay(TaskThumbnailViewDeprecated thumbnailView) {
-        return new TaskOverlayGo(thumbnailView, mContentRequester);
+    public TaskOverlayGo createOverlay(TaskContainer taskContainer) {
+        return new TaskOverlayGo(taskContainer, mContentRequester);
     }
 
     /**
@@ -120,9 +120,9 @@
         private OverlayDialogGo mDialog;
         private ArrowTipView mArrowTipView;
 
-        private TaskOverlayGo(TaskThumbnailViewDeprecated taskThumbnailView,
+        private TaskOverlayGo(TaskContainer taskContainer,
                 AssistContentRequester assistContentRequester) {
-            super(taskThumbnailView);
+            super(taskContainer);
             mFactoryContentRequester = assistContentRequester;
             mSharedPreferences = LauncherPrefs.getPrefs(mApplicationContext);
         }
@@ -148,7 +148,8 @@
             // Disable Overview Actions for Work Profile apps
             boolean isManagedProfileTask =
                     UserManager.get(mApplicationContext).isManagedProfile(task.key.userId);
-            boolean isAllowedByPolicy = mThumbnailView.isRealSnapshot() && !isManagedProfileTask;
+            boolean isAllowedByPolicy = mTaskContainer.getThumbnailViewDeprecated().isRealSnapshot()
+                    && !isManagedProfileTask;
             getActionsView().setCallbacks(new OverlayUICallbacksGoImpl(isAllowedByPolicy, task));
             mTaskPackageName = task.key.getPackageName();
             mSharedPreferences = LauncherPrefs.getPrefs(mApplicationContext);
@@ -162,8 +163,7 @@
             int taskId = task.key.id;
             mFactoryContentRequester.requestAssistContent(taskId, this::onAssistContentReceived);
 
-            RecentsOrientedState orientedState =
-                    mThumbnailView.getTaskView().getRecentsView().getPagedViewOrientedState();
+            RecentsOrientedState orientedState = mTaskContainer.getTaskView().getOrientedState();
             boolean isInLandscape = orientedState.getDisplayRotation() != ROTATION_0;
 
             // show tooltips in portrait mode only
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
index d0c494c..2b68b52 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
@@ -269,8 +269,8 @@
                                     foundTaskView,
                                     foundTask,
                                     taskContainer.getIconView().getDrawable(),
-                                    taskContainer.getThumbnailView(),
-                                    taskContainer.getThumbnailView().getThumbnail(),
+                                    taskContainer.getThumbnailViewDeprecated(),
+                                    taskContainer.getThumbnailViewDeprecated().getThumbnail(),
                                     null /* intent */,
                                     null /* user */,
                                     info);
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index e379b2a..c33e4cc 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -542,16 +542,18 @@
         if (mDesktopVisibilityController != null) {
             mDesktopVisibilityController.unregisterSystemUiListener();
         }
+        mDesktopVisibilityController = null;
 
         if (mSplitSelectStateController != null) {
+            removeBackAnimationCallback(mSplitSelectStateController.getSplitBackHandler());
             mSplitSelectStateController.onDestroy();
         }
+        mSplitSelectStateController = null;
 
         super.onDestroy();
         mHotseatPredictionController.destroy();
         mSplitWithKeyboardShortcutController.onDestroy();
         if (mViewCapture != null) mViewCapture.close();
-        removeBackAnimationCallback(mSplitSelectStateController.getSplitBackHandler());
     }
 
     @Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
index 1a98db1..93e4fbd 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
@@ -151,7 +151,7 @@
             int sysuiFlags = 0;
             TaskView tv = mOverviewPanel.getTaskViewAt(0);
             if (tv != null) {
-                sysuiFlags = tv.getFirstThumbnailView().getSysUiStatusNavFlags();
+                sysuiFlags = tv.getFirstThumbnailViewDeprecated().getSysUiStatusNavFlags();
             }
             mLauncher.getSystemUiController().updateUiState(UI_STATE_FULLSCREEN_TASK, sysuiFlags);
         } else {
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
index 26b528c..4bc3c16 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
@@ -252,7 +252,7 @@
                     mTaskBeingDragged, maxDuration, currentInterpolator);
 
             // Since the thumbnail is what is filling the screen, based the end displacement on it.
-            View thumbnailView = mTaskBeingDragged.getFirstThumbnailView();
+            View thumbnailView = mTaskBeingDragged.getFirstThumbnailViewDeprecated();
             mTempCords[1] = orientationHandler.getSecondaryDimension(thumbnailView);
             dl.getDescendantCoordRelativeToSelf(thumbnailView, mTempCords);
             mEndDisplacement = secondaryLayerDimension - mTempCords[1];
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 10e327d..463222d 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -107,7 +107,6 @@
 import com.android.launcher3.dragndrop.DragView;
 import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.logging.StatsLogManager.StatsLogger;
-import com.android.launcher3.statehandlers.DesktopVisibilityController;
 import com.android.launcher3.statemanager.BaseState;
 import com.android.launcher3.taskbar.TaskbarThresholdUtils;
 import com.android.launcher3.taskbar.TaskbarUIController;
@@ -926,7 +925,7 @@
             TaskView runningTask = mRecentsView.getRunningTaskView();
             TaskView centermostTask = mRecentsView.getTaskViewNearestToCenterOfScreen();
             int centermostTaskFlags = centermostTask == null ? 0
-                    : centermostTask.getFirstThumbnailView().getSysUiStatusNavFlags();
+                    : centermostTask.getFirstThumbnailViewDeprecated().getSysUiStatusNavFlags();
             boolean swipeUpThresholdPassed = windowProgress > 1 - UPDATE_SYSUI_FLAGS_THRESHOLD;
             boolean quickswitchThresholdPassed = centermostTask != runningTask;
 
@@ -1478,7 +1477,7 @@
                         ? SysUiStatsLog.LAUNCHER_UICHANGED__INPUT_TYPE__TRACKPAD
                         : SysUiStatsLog.LAUNCHER_UICHANGED__INPUT_TYPE__TOUCH);
         if (targetTask != null) {
-            logger.withItemInfo(targetTask.getItemInfo());
+            logger.withItemInfo(targetTask.getFirstItemInfo());
         }
 
         int pageIndex = endTarget == LAST_TASK || mRecentsView == null
@@ -2424,7 +2423,8 @@
         RemoteAnimationTarget taskTarget = taskTargetOptional.get();
         TaskView taskView = mRecentsView == null
                 ? null : mRecentsView.getTaskViewByTaskId(taskTarget.taskId);
-        if (taskView == null || !taskView.getFirstThumbnailView().shouldShowSplashView()) {
+        if (taskView == null
+                || !taskView.getFirstThumbnailViewDeprecated().shouldShowSplashView()) {
             ActiveGestureLog.INSTANCE.addLog("Invalid task view splash state");
             finishRecentsAnimationOnTasksAppeared(null /* onFinishComplete */);
             return;
diff --git a/quickstep/src/com/android/quickstep/DesktopSystemShortcut.kt b/quickstep/src/com/android/quickstep/DesktopSystemShortcut.kt
index fdf4574..50a06fc 100644
--- a/quickstep/src/com/android/quickstep/DesktopSystemShortcut.kt
+++ b/quickstep/src/com/android/quickstep/DesktopSystemShortcut.kt
@@ -29,24 +29,24 @@
 /** A menu item, "Desktop", that allows the user to bring the current app into Desktop Windowing. */
 class DesktopSystemShortcut(
     container: RecentsViewContainer,
-    private val mTaskContainer: TaskContainer,
+    private val taskContainer: TaskContainer,
     abstractFloatingViewHelper: AbstractFloatingViewHelper
 ) :
     SystemShortcut<RecentsViewContainer>(
         R.drawable.ic_caption_desktop_button_foreground,
         R.string.recent_task_option_desktop,
         container,
-        mTaskContainer.itemInfo,
-        mTaskContainer.taskView,
+        taskContainer.itemInfo,
+        taskContainer.taskView,
         abstractFloatingViewHelper
     ) {
     override fun onClick(view: View) {
         dismissTaskMenuView()
-        val recentsView = mTarget!!.getOverviewPanel<RecentsView<*, *>>()
-        recentsView.moveTaskToDesktop(mTaskContainer) {
+        val recentsView = mTarget.getOverviewPanel<RecentsView<*, *>>()
+        recentsView.moveTaskToDesktop(taskContainer) {
             mTarget.statsLogManager
                 .logger()
-                .withItemInfo(mTaskContainer.itemInfo)
+                .withItemInfo(taskContainer.itemInfo)
                 .log(LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_DESKTOP_TAP)
         }
     }
diff --git a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
index 1a46fb6..b183ae3 100644
--- a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
@@ -80,8 +80,9 @@
         return shortcuts;
     }
 
-    public TaskOverlay createOverlay(TaskThumbnailViewDeprecated thumbnailView) {
-        return new TaskOverlay(thumbnailView);
+    /** Creates a {@link TaskOverlay} associated with the provide {@link TaskContainer}. */
+    public TaskOverlay<?> createOverlay(TaskContainer taskContainer) {
+        return new TaskOverlay<>(taskContainer);
     }
 
     /**
@@ -124,28 +125,29 @@
     public static class TaskOverlay<T extends OverviewActionsView> {
 
         protected final Context mApplicationContext;
-        protected final TaskThumbnailViewDeprecated mThumbnailView;
+        protected final TaskContainer mTaskContainer;
 
         private T mActionsView;
         protected ImageActionsApi mImageApi;
 
-        protected TaskOverlay(TaskThumbnailViewDeprecated taskThumbnailViewDeprecated) {
-            mApplicationContext = taskThumbnailViewDeprecated.getContext().getApplicationContext();
-            mThumbnailView = taskThumbnailViewDeprecated;
+        protected TaskOverlay(TaskContainer taskContainer) {
+            mApplicationContext = taskContainer.getTaskView().getContext().getApplicationContext();
+            mTaskContainer = taskContainer;
             mImageApi = new ImageActionsApi(
-                    mApplicationContext, mThumbnailView::getThumbnail);
+                    mApplicationContext, mTaskContainer.getThumbnailViewDeprecated()::getThumbnail);
         }
 
         protected T getActionsView() {
             if (mActionsView == null) {
-                mActionsView = BaseActivity.fromContext(mThumbnailView.getContext()).findViewById(
+                mActionsView = BaseActivity.fromContext(
+                        mTaskContainer.getThumbnailViewDeprecated().getContext()).findViewById(
                         R.id.overview_actions_view);
             }
             return mActionsView;
         }
 
         public TaskThumbnailViewDeprecated getThumbnailView() {
-            return mThumbnailView;
+            return mTaskContainer.getThumbnailViewDeprecated();
         }
 
         /**
@@ -157,7 +159,8 @@
 
             if (thumbnail != null) {
                 getActionsView().updateDisabledFlags(DISABLED_ROTATED, rotated);
-                boolean isAllowedByPolicy = mThumbnailView.isRealSnapshot();
+                boolean isAllowedByPolicy =
+                        mTaskContainer.getThumbnailViewDeprecated().isRealSnapshot();
                 getActionsView().setCallbacks(new OverlayUICallbacksImpl(isAllowedByPolicy, task));
             }
         }
@@ -168,7 +171,8 @@
          * @param callback callback to run, after switching to screenshot
          */
         public void endLiveTileMode(@NonNull Runnable callback) {
-            RecentsView recentsView = mThumbnailView.getTaskView().getRecentsView();
+            RecentsView recentsView =
+                    mTaskContainer.getThumbnailViewDeprecated().getTaskView().getRecentsView();
             // Task has already been dismissed
             if (recentsView == null) return;
             recentsView.switchToScreenshot(
@@ -181,8 +185,8 @@
          */
         @SuppressLint("NewApi")
         protected void saveScreenshot(Task task) {
-            if (mThumbnailView.isRealSnapshot()) {
-                mImageApi.saveScreenshot(mThumbnailView.getThumbnail(),
+            if (mTaskContainer.getThumbnailViewDeprecated().isRealSnapshot()) {
+                mImageApi.saveScreenshot(mTaskContainer.getThumbnailViewDeprecated().getThumbnail(),
                         getTaskSnapshotBounds(), getTaskSnapshotInsets(), task.key);
             } else {
                 showBlockedByPolicyMessage();
@@ -190,14 +194,17 @@
         }
 
         protected void enterSplitSelect() {
-            RecentsView overviewPanel = mThumbnailView.getTaskView().getRecentsView();
+            RecentsView overviewPanel =
+                    mTaskContainer.getThumbnailViewDeprecated().getTaskView().getRecentsView();
             // Task has already been dismissed
             if (overviewPanel == null) return;
-            overviewPanel.initiateSplitSelect(mThumbnailView.getTaskView());
+            overviewPanel.initiateSplitSelect(
+                    mTaskContainer.getThumbnailViewDeprecated().getTaskView());
         }
 
         protected void saveAppPair() {
-            GroupedTaskView taskView = (GroupedTaskView) mThumbnailView.getTaskView();
+            GroupedTaskView taskView =
+                    (GroupedTaskView) mTaskContainer.getThumbnailViewDeprecated().getTaskView();
             taskView.getRecentsView().getSplitSelectController().getAppPairsController()
                     .saveAppPair(taskView);
         }
@@ -243,10 +250,11 @@
          */
         public Rect getTaskSnapshotBounds() {
             int[] location = new int[2];
-            mThumbnailView.getLocationOnScreen(location);
+            mTaskContainer.getThumbnailViewDeprecated().getLocationOnScreen(location);
 
-            return new Rect(location[0], location[1], mThumbnailView.getWidth() + location[0],
-                    mThumbnailView.getHeight() + location[1]);
+            return new Rect(location[0], location[1],
+                    mTaskContainer.getThumbnailViewDeprecated().getWidth() + location[0],
+                    mTaskContainer.getThumbnailViewDeprecated().getHeight() + location[1]);
         }
 
         /**
@@ -256,7 +264,7 @@
          */
         @RequiresApi(api = Build.VERSION_CODES.Q)
         public Insets getTaskSnapshotInsets() {
-            return mThumbnailView.getScaledInsets();
+            return mTaskContainer.getThumbnailViewDeprecated().getScaledInsets();
         }
 
         /**
@@ -267,17 +275,21 @@
 
         protected void showBlockedByPolicyMessage() {
             ActivityContext activityContext = ActivityContext.lookupContext(
-                    mThumbnailView.getContext());
+                    mTaskContainer.getThumbnailViewDeprecated().getContext());
             String message = activityContext.getStringCache() != null
                     ? activityContext.getStringCache().disabledByAdminMessage
-                    : mThumbnailView.getContext().getString(R.string.blocked_by_policy);
+                    : mTaskContainer.getThumbnailViewDeprecated().getContext().getString(
+                            R.string.blocked_by_policy);
 
-            Snackbar.show(BaseActivity.fromContext(mThumbnailView.getContext()), message, null);
+            Snackbar.show(BaseActivity.fromContext(
+                    mTaskContainer.getThumbnailViewDeprecated().getContext()), message, null);
         }
 
         /** Called when the snapshot has updated its full screen drawing parameters. */
-        public void setFullscreenParams(TaskView.FullscreenDrawParams fullscreenParams) {
-        }
+        public void setFullscreenParams(TaskView.FullscreenDrawParams fullscreenParams) {}
+
+        /** Sets visibility for the overlay associated elements. */
+        public void setVisibility(int visibility) {}
 
         private class ScreenshotSystemShortcut extends SystemShortcut {
 
@@ -292,7 +304,8 @@
 
             @Override
             public void onClick(View view) {
-                saveScreenshot(mThumbnailView.getTaskView().getFirstTask());
+                saveScreenshot(
+                        mTaskContainer.getThumbnailViewDeprecated().getTaskView().getFirstTask());
                 dismissTaskMenuView();
             }
         }
diff --git a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
index 537f432..4b5c826 100644
--- a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
@@ -128,7 +128,8 @@
 
         public SplitSelectSystemShortcut(RecentsViewContainer container, TaskView taskView,
                 SplitPositionOption option) {
-            super(option.iconResId, option.textResId, container, taskView.getItemInfo(), taskView);
+            super(option.iconResId, option.textResId, container, taskView.getFirstItemInfo(),
+                    taskView);
             mTaskView = taskView;
             mSplitPositionOption = option;
         }
@@ -149,8 +150,8 @@
 
         public SaveAppPairSystemShortcut(RecentsViewContainer container, GroupedTaskView taskView,
             int iconResId) {
-                super(iconResId, R.string.save_app_pair, container,
-                    taskView.getItemInfo(), taskView);
+            super(iconResId, R.string.save_app_pair, container, taskView.getFirstItemInfo(),
+                    taskView);
             mTaskView = taskView;
         }
 
@@ -180,7 +181,7 @@
             mHandler = new Handler(Looper.getMainLooper());
             mTaskView = taskContainer.getTaskView();
             mRecentsView = container.getOverviewPanel();
-            mThumbnailView = taskContainer.getThumbnailView();
+            mThumbnailView = taskContainer.getThumbnailViewDeprecated();
         }
 
         @Override
@@ -240,7 +241,7 @@
                 overridePendingAppTransitionMultiThumbFuture(
                         future, animStartedListener, mHandler, true /* scaleUp */,
                         taskKey.displayId);
-                mTarget.getStatsLogManager().logger().withItemInfo(mTaskView.getItemInfo())
+                mTarget.getStatsLogManager().logger().withItemInfo(mTaskView.getFirstItemInfo())
                         .log(mLauncherEvent);
             }
         }
@@ -424,7 +425,7 @@
                         mTaskView.getFirstTask().key.id);
             }
             dismissTaskMenuView();
-            mTarget.getStatsLogManager().logger().withItemInfo(mTaskView.getItemInfo())
+            mTarget.getStatsLogManager().logger().withItemInfo(mTaskView.getFirstItemInfo())
                     .log(LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_PIN_TAP);
         }
     }
@@ -469,10 +470,8 @@
                 }
             }
 
-            SystemShortcut screenshotShortcut =
-                    taskContainer.getThumbnailView().getTaskOverlay()
-                            .getScreenshotShortcut(container, taskContainer.getItemInfo(),
-                                    taskContainer.getTaskView());
+            SystemShortcut screenshotShortcut = taskContainer.getOverlay().getScreenshotShortcut(
+                    container, taskContainer.getItemInfo(), taskContainer.getTaskView());
             return createSingletonShortcutList(screenshotShortcut);
         }
 
@@ -503,9 +502,8 @@
             }
 
             SystemShortcut modalStateSystemShortcut =
-                    taskContainer.getThumbnailView().getTaskOverlay()
-                            .getModalStateSystemShortcut(
-                                    taskContainer.getItemInfo(), taskContainer.getTaskView());
+                    taskContainer.getOverlay().getModalStateSystemShortcut(
+                            taskContainer.getItemInfo(), taskContainer.getTaskView());
             return createSingletonShortcutList(modalStateSystemShortcut);
         }
     };
diff --git a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
index 40ea70f..8243ede 100644
--- a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
+++ b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
@@ -118,8 +118,8 @@
                 if (container.task.getKey().getId() == splitSelectStateController.initialTaskId) {
                     val drawable = getDrawable(container.iconView, splitSelectSource)
                     return SplitAnimInitProps(
-                        container.thumbnailView,
-                        container.thumbnailView.thumbnail,
+                        container.thumbnailViewDeprecated,
+                        container.thumbnailViewDeprecated.thumbnail,
                         drawable!!,
                         fadeWithThumbnail = true,
                         isStagedTask = true,
@@ -137,8 +137,8 @@
             taskView.taskContainers.first().let {
                 val drawable = getDrawable(it.iconView, splitSelectSource)
                 return SplitAnimInitProps(
-                    it.thumbnailView,
-                    it.thumbnailView.thumbnail,
+                    it.thumbnailViewDeprecated,
+                    it.thumbnailViewDeprecated.thumbnail,
                     drawable!!,
                     fadeWithThumbnail = true,
                     isStagedTask = true,
@@ -182,7 +182,7 @@
         taskViewHeight: Int,
         isPrimaryTaskSplitting: Boolean
     ) {
-        val thumbnail = taskIdAttributeContainer.thumbnailView
+        val thumbnail = taskIdAttributeContainer.thumbnailViewDeprecated
         val iconView: View = taskIdAttributeContainer.iconView.asView()
         builder.add(ObjectAnimator.ofFloat(thumbnail, TaskThumbnailViewDeprecated.SPLASH_ALPHA, 1f))
         thumbnail.setShowSplashForSplitSelection(true)
diff --git a/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt b/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt
index 3935c67..936f6a1 100644
--- a/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt
+++ b/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt
@@ -28,24 +28,20 @@
 import android.view.ViewGroup
 import androidx.core.view.updateLayoutParams
 import com.android.launcher3.R
-import com.android.launcher3.util.CancellableTask
 import com.android.launcher3.util.RunnableList
 import com.android.launcher3.util.SplitConfigurationOptions
+import com.android.launcher3.util.TransformingTouchDelegate
 import com.android.launcher3.util.ViewPool
 import com.android.launcher3.util.rects.set
 import com.android.quickstep.BaseContainerInterface
-import com.android.quickstep.RecentsModel
 import com.android.quickstep.TaskOverlayFactory
 import com.android.quickstep.util.RecentsOrientedState
 import com.android.systemui.shared.recents.model.Task
-import com.android.systemui.shared.recents.model.ThumbnailData
 
 /** TaskView that contains all tasks that are part of the desktop. */
-// TODO(b/249371338): TaskView needs to be refactored to have better support for N tasks.
 class DesktopTaskView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
     TaskView(context, attrs) {
 
-    private val pendingThumbnailRequests = mutableListOf<CancellableTask<*>>()
     private val snapshotDrawParams =
         object : FullscreenDrawParams(context) {
             // DesktopTaskView thumbnail's corner radius is independent of fullscreenProgress.
@@ -57,31 +53,39 @@
             context,
             this,
             R.layout.task_thumbnail,
-            10,
-            0 // As DesktopTaskView is inflated in background, use initialSize=0 to avoid initPool.
+            VIEW_POOL_MAX_SIZE,
+            VIEW_POOL_INITIAL_SIZE
         )
     private val tempPointF = PointF()
     private val tempRect = Rect()
     private lateinit var backgroundView: View
+    private lateinit var iconView: TaskViewIcon
     private var childCountAtInflation = 0
 
     override fun onFinishInflate() {
         super.onFinishInflate()
-
-        backgroundView = findViewById(R.id.background)!!
-        val topMarginPx = container.deviceProfile.overviewTaskThumbnailTopMarginPx
-        backgroundView.updateLayoutParams<LayoutParams> { topMargin = topMarginPx }
-
-        val outerRadii = FloatArray(8) { taskCornerRadius }
-        backgroundView.background =
-            ShapeDrawable(RoundRectShape(outerRadii, null, null)).apply {
-                setTint(resources.getColor(android.R.color.system_neutral2_300, context.theme))
+        backgroundView =
+            findViewById<View>(R.id.background)!!.apply {
+                updateLayoutParams<LayoutParams> {
+                    topMargin = container.deviceProfile.overviewTaskThumbnailTopMarginPx
+                }
+                background =
+                    ShapeDrawable(RoundRectShape(FloatArray(8) { taskCornerRadius }, null, null))
+                        .apply {
+                            setTint(
+                                resources.getColor(
+                                    android.R.color.system_neutral2_300,
+                                    context.theme
+                                )
+                            )
+                        }
             }
-
-        val iconBackground = resources.getDrawable(R.drawable.bg_circle, context.theme)
-        val icon = resources.getDrawable(R.drawable.ic_desktop, context.theme)
-        setIcon(iconView, LayerDrawable(arrayOf(iconBackground, icon)))
-
+        iconView =
+            getOrInflateIconView(R.id.icon).apply {
+                val iconBackground = resources.getDrawable(R.drawable.bg_circle, context.theme)
+                val icon = resources.getDrawable(R.drawable.ic_desktop, context.theme)
+                setIcon(this, LayerDrawable(arrayOf(iconBackground, icon)))
+            }
         childCountAtInflation = childCount
     }
 
@@ -124,7 +128,7 @@
                     }
             val thumbWidth = (taskSize.width() * scaleWidth).toInt()
             val thumbHeight = (taskSize.height() * scaleHeight).toInt()
-            it.thumbnailView.measure(
+            it.thumbnailViewDeprecated.measure(
                 MeasureSpec.makeMeasureSpec(thumbWidth, MeasureSpec.EXACTLY),
                 MeasureSpec.makeMeasureSpec(thumbHeight, MeasureSpec.EXACTLY)
             )
@@ -135,8 +139,8 @@
             var taskY = (positionInParent.y * scaleHeight).toInt()
             // move task down by margin size
             taskY += thumbnailTopMarginPx
-            it.thumbnailView.x = taskX.toFloat()
-            it.thumbnailView.y = taskY.toFloat()
+            it.thumbnailViewDeprecated.x = taskX.toFloat()
+            it.thumbnailViewDeprecated.y = taskY.toFloat()
             if (DEBUG) {
                 Log.d(
                     TAG,
@@ -148,23 +152,10 @@
     }
 
     override fun onRecycle() {
-        resetPersistentViewTransforms()
-        // Clear any references to the thumbnail (it will be re-read either from the cache or the
-        // system on next bind)
-        taskContainers.forEach { it.thumbnailView.setThumbnail(it.task, null) }
-        setOverlayEnabled(false)
-        onTaskListVisibilityChanged(false)
+        super.onRecycle()
         visibility = VISIBLE
     }
 
-    override fun bind(
-        task: Task,
-        orientedState: RecentsOrientedState,
-        taskOverlayFactory: TaskOverlayFactory
-    ) {
-        bind(listOf(task), orientedState, taskOverlayFactory)
-    }
-
     /** Updates this desktop task to the gives task list defined in `tasks` */
     fun bind(
         tasks: List<Task>,
@@ -185,12 +176,12 @@
         val taskContainers = taskContainers as ArrayList
         taskContainers.ensureCapacity(tasks.size)
         tasks.forEachIndexed { index, task ->
-            val thumbnailView: TaskThumbnailViewDeprecated
+            val thumbnailViewDeprecated: TaskThumbnailViewDeprecated
             if (index >= taskContainers.size) {
-                thumbnailView = taskThumbnailViewPool.view
+                thumbnailViewDeprecated = taskThumbnailViewPool.view
                 // Add thumbnailView from to position after the initial child views.
                 addView(
-                    thumbnailView,
+                    thumbnailViewDeprecated,
                     childCountAtInflation,
                     LayoutParams(
                         ViewGroup.LayoutParams.WRAP_CONTENT,
@@ -198,17 +189,22 @@
                     )
                 )
             } else {
-                thumbnailView = taskContainers[index].thumbnailView
+                thumbnailViewDeprecated = taskContainers[index].thumbnailViewDeprecated
             }
-            thumbnailView.bind(task, taskOverlayFactory)
             val taskContainer =
                 TaskContainer(
-                    task,
-                    thumbnailView,
-                    iconView,
-                    SplitConfigurationOptions.STAGE_POSITION_UNDEFINED,
-                    null
-                )
+                        task,
+                        // TODO(b/338360089): Support new TTV for DesktopTaskView
+                        thumbnailView = null,
+                        thumbnailViewDeprecated,
+                        iconView,
+                        TransformingTouchDelegate(iconView.asView()),
+                        SplitConfigurationOptions.STAGE_POSITION_UNDEFINED,
+                        digitalWellBeingToast = null,
+                        showWindowsView = null,
+                        taskOverlayFactory
+                    )
+                    .apply { thumbnailViewDeprecated.bind(task, overlay) }
             if (index >= taskContainers.size) {
                 taskContainers.add(taskContainer)
             } else {
@@ -216,42 +212,20 @@
             }
         }
         repeat(taskContainers.size - tasks.size) {
-            taskContainers.removeLast().apply {
-                removeView(thumbnailView)
-                taskThumbnailViewPool.recycle(thumbnailView)
+            with(taskContainers.removeLast()) {
+                removeView(thumbnailViewDeprecated)
+                taskThumbnailViewPool.recycle(thumbnailViewDeprecated)
             }
         }
 
         setOrientationState(orientedState)
     }
 
+    override fun needsUpdate(dataChange: Int, flag: Int) =
+        if (flag == FLAG_UPDATE_THUMBNAIL) super.needsUpdate(dataChange, flag) else false
+
     // thumbnailView is laid out differently and is handled in onMeasure
-    override fun setThumbnailOrientation(orientationState: RecentsOrientedState) {}
-
-    override fun onTaskListVisibilityChanged(visible: Boolean, changes: Int) {
-        cancelPendingLoadTasks()
-        if (needsUpdate(changes, FLAG_UPDATE_THUMBNAIL)) {
-            taskContainers.forEach {
-                if (visible) {
-                    RecentsModel.INSTANCE.get(context)
-                        .thumbnailCache
-                        .updateThumbnailInBackground(it.task) { thumbnailData: ThumbnailData ->
-                            it.thumbnailView.setThumbnail(it.task, thumbnailData)
-                        }
-                        ?.apply { pendingThumbnailRequests.add(this) }
-                } else {
-                    it.thumbnailView.setThumbnail(null, null)
-                    // Reset the task thumbnail ref
-                    it.task.thumbnail = null
-                }
-            }
-        }
-    }
-
-    override fun cancelPendingLoadTasks() {
-        pendingThumbnailRequests.forEach { it.cancel() }
-        pendingThumbnailRequests.clear()
-    }
+    override fun updateThumbnailSize() {}
 
     override fun getThumbnailBounds(bounds: Rect, relativeToDragLayer: Boolean) {
         if (relativeToDragLayer) {
@@ -279,59 +253,30 @@
         callback(true)
     }
 
-    override fun refreshThumbnails(thumbnailDatas: HashMap<Int, ThumbnailData?>?) {
-        // Sets new thumbnails based on the incoming data and refreshes the rest.
-        thumbnailDatas?.let {
-            taskContainers.forEach {
-                val thumbnailData = thumbnailDatas[it.task.key.id]
-                if (thumbnailData != null) {
-                    it.thumbnailView.setThumbnail(it.task, thumbnailData)
-                } else {
-                    // Refresh the rest that were not updated.
-                    it.thumbnailView.refresh()
-                }
-            }
-        }
-    }
-
     // Desktop tile can't be in split screen
     override fun confirmSecondSplitSelectApp(): Boolean = false
 
-    override fun setColorTint(amount: Float, tintColor: Int) {
-        taskContainers.forEach { it.thumbnailView.dimAlpha = amount }
-    }
-
-    override fun setThumbnailVisibility(visibility: Int, taskId: Int) {
-        taskContainers.forEach { it.thumbnailView.visibility = visibility }
-    }
-
     // TODO(b/330685808) support overlay for Screenshot action
     override fun setOverlayEnabled(overlayEnabled: Boolean) {}
 
     override fun onFullscreenProgressChanged(fullscreenProgress: Float) {
-        // TODO(b/249371338): this copies parent implementation and makes it work for N thumbs
-        iconView.setVisibility(if (fullscreenProgress < 1) VISIBLE else INVISIBLE)
         // Don't show background while we are transitioning to/from fullscreen
         backgroundView.visibility = if (fullscreenProgress > 0) INVISIBLE else VISIBLE
-        taskContainers.forEach {
-            it.thumbnailView.taskOverlay.setFullscreenProgress(fullscreenProgress)
-        }
-        setIconsAndBannersFullscreenProgress(fullscreenProgress)
-        updateSnapshotRadius()
     }
 
-    override fun updateSnapshotRadius() {
+    override fun updateCurrentFullscreenParams() {
+        super.updateCurrentFullscreenParams()
         updateFullscreenParams(snapshotDrawParams)
-        taskContainers.forEach { it.thumbnailView.setFullscreenParams(snapshotDrawParams) }
     }
 
-    override fun applyThumbnailSplashAlpha() {
-        taskContainers.forEach { it.thumbnailView.setSplashAlpha(taskThumbnailSplashAlpha) }
-    }
+    override fun getThumbnailFullscreenParams() = snapshotDrawParams
 
     companion object {
         private const val TAG = "DesktopTaskView"
         private const val DEBUG = false
+        private const val VIEW_POOL_MAX_SIZE = 10
+        // As DesktopTaskView is inflated in background, use initialSize=0 to avoid initPool.
+        private const val VIEW_POOL_INITIAL_SIZE = 0
         private val ORIGIN = Point(0, 0)
     }
 }
diff --git a/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java b/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java
index 4df9414..a8ebe51 100644
--- a/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java
+++ b/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java
@@ -320,7 +320,7 @@
                 (FrameLayout.LayoutParams) mBanner.getLayoutParams();
         DeviceProfile deviceProfile = mContainer.getDeviceProfile();
         layoutParams.bottomMargin = ((ViewGroup.MarginLayoutParams)
-                mTaskView.getFirstThumbnailView().getLayoutParams()).bottomMargin;
+                mTaskView.getFirstThumbnailViewDeprecated().getLayoutParams()).bottomMargin;
         RecentsPagedOrientationHandler orientationHandler = mTaskView.getPagedOrientationHandler();
         Pair<Float, Float> translations = orientationHandler
                 .getDwbLayoutTranslations(mTaskView.getMeasuredWidth(),
diff --git a/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt b/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt
index f6ae038..efbfa09 100644
--- a/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt
+++ b/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt
@@ -18,31 +18,24 @@
 import android.app.ActivityTaskManager.INVALID_TASK_ID
 import android.content.Context
 import android.graphics.PointF
-import android.graphics.Rect
 import android.util.AttributeSet
 import android.util.Log
-import android.view.MotionEvent
 import android.view.View
-import android.view.ViewStub
 import com.android.internal.jank.Cuj
 import com.android.launcher3.Flags.enableOverviewIconMenu
 import com.android.launcher3.R
 import com.android.launcher3.Utilities
 import com.android.launcher3.config.FeatureFlags
-import com.android.launcher3.util.CancellableTask
 import com.android.launcher3.util.RunnableList
 import com.android.launcher3.util.SplitConfigurationOptions
 import com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT
 import com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT
 import com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_UNDEFINED
-import com.android.launcher3.util.TransformingTouchDelegate
-import com.android.quickstep.RecentsModel
 import com.android.quickstep.TaskOverlayFactory
 import com.android.quickstep.util.RecentsOrientedState
 import com.android.quickstep.util.SplitScreenUtils.Companion.convertLauncherSplitBoundsToShell
 import com.android.quickstep.util.SplitSelectStateController
 import com.android.systemui.shared.recents.model.Task
-import com.android.systemui.shared.recents.model.ThumbnailData
 import com.android.systemui.shared.recents.utilities.PreviewPositionHelper
 import com.android.systemui.shared.system.InteractionJankMonitorWrapper
 import com.android.wm.shell.common.split.SplitScreenConstants.PersistentSnapPosition
@@ -60,42 +53,14 @@
 class GroupedTaskView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
     TaskView(context, attrs) {
     // TODO(b/336612373): Support new TTV for GroupedTaskView
-    private val icon2CenterCoordinates = FloatArray(2)
-    private val digitalWellBeingToast2: DigitalWellBeingToast =
-        DigitalWellBeingToast(container, this)
-
-    private lateinit var snapshotView2: TaskThumbnailViewDeprecated
-    private lateinit var iconView2: TaskViewIcon
-    private lateinit var icon2TouchDelegate: TransformingTouchDelegate
-
     var splitBoundsConfig: SplitConfigurationOptions.SplitBounds? = null
         private set
-    private var thumbnailLoadRequest2: CancellableTask<ThumbnailData>? = null
-    private var iconLoadRequest2: CancellableTask<*>? = null
 
     @get:PersistentSnapPosition
     val snapPosition: Int
         /** Returns the [PersistentSnapPosition] of this pair of tasks. */
         get() = splitBoundsConfig?.snapPosition ?: STAGE_POSITION_UNDEFINED
 
-    @get:Deprecated("Use {@link #mTaskContainers} instead.")
-    private val secondTask: Task
-        /** Returns the second task bound to this TaskView. */
-        get() = taskContainers[1].task
-
-    override fun onFinishInflate() {
-        super.onFinishInflate()
-        snapshotView2 = findViewById(R.id.bottomright_snapshot)!!
-        val iconViewStub2 =
-            findViewById<ViewStub>(R.id.bottomRight_icon)!!.apply {
-                layoutResource =
-                    if (enableOverviewIconMenu()) R.layout.icon_app_chip_view
-                    else R.layout.icon_view
-            }
-        iconView2 = iconViewStub2.inflate() as TaskViewIcon
-        icon2TouchDelegate = TransformingTouchDelegate(iconView2.asView())
-    }
-
     override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
         super.onMeasure(widthMeasureSpec, heightMeasureSpec)
         val widthSize = MeasureSpec.getSize(widthMeasureSpec)
@@ -105,8 +70,8 @@
         val initSplitTaskId = getThisTaskCurrentlyInSplitSelection()
         if (initSplitTaskId == INVALID_TASK_ID) {
             pagedOrientationHandler.measureGroupedTaskViewThumbnailBounds(
-                taskThumbnailViewDeprecated,
-                snapshotView2,
+                taskContainers[0].thumbnailViewDeprecated,
+                taskContainers[1].thumbnailViewDeprecated,
                 widthSize,
                 heightSize,
                 splitBoundsConfig,
@@ -117,20 +82,24 @@
             // The following only applies to large screen for now, but for future reference
             // we'd want to abstract this out in PagedViewHandlers to get the primary/secondary
             // translation directions
-            taskThumbnailViewDeprecated.applySplitSelectTranslateX(
-                taskThumbnailViewDeprecated.translationX
-            )
-            taskThumbnailViewDeprecated.applySplitSelectTranslateY(
-                taskThumbnailViewDeprecated.translationY
-            )
-            snapshotView2.applySplitSelectTranslateX(snapshotView2.translationX)
-            snapshotView2.applySplitSelectTranslateY(snapshotView2.translationY)
+            taskContainers[0]
+                .thumbnailViewDeprecated
+                .applySplitSelectTranslateX(taskContainers[0].thumbnailViewDeprecated.translationX)
+            taskContainers[0]
+                .thumbnailViewDeprecated
+                .applySplitSelectTranslateY(taskContainers[0].thumbnailViewDeprecated.translationY)
+            taskContainers[1]
+                .thumbnailViewDeprecated
+                .applySplitSelectTranslateX(taskContainers[1].thumbnailViewDeprecated.translationX)
+            taskContainers[1]
+                .thumbnailViewDeprecated
+                .applySplitSelectTranslateY(taskContainers[1].thumbnailViewDeprecated.translationY)
         } else {
             // Currently being split with this taskView, let the non-split selected thumbnail
             // take up full thumbnail area
             taskContainers
                 .firstOrNull { it.task.key.id != initSplitTaskId }
-                ?.thumbnailView
+                ?.thumbnailViewDeprecated
                 ?.measure(
                     widthMeasureSpec,
                     MeasureSpec.makeMeasureSpec(
@@ -146,7 +115,6 @@
 
     override fun onRecycle() {
         super.onRecycle()
-        snapshotView2.setThumbnail(secondTask, null)
         splitBoundsConfig = null
     }
 
@@ -158,30 +126,42 @@
         splitBoundsConfig: SplitConfigurationOptions.SplitBounds?,
     ) {
         cancelPendingLoadTasks()
-        setupTaskContainers(primaryTask, taskOverlayFactory)
         taskContainers =
             listOf(
-                taskContainers[0].apply { stagePosition = STAGE_POSITION_TOP_OR_LEFT },
-                TaskContainer(
+                createTaskContainer(
+                    primaryTask,
+                    R.id.snapshot,
+                    R.id.icon,
+                    R.id.show_windows,
+                    STAGE_POSITION_TOP_OR_LEFT,
+                    taskOverlayFactory
+                ),
+                createTaskContainer(
                     secondaryTask,
-                    findViewById(R.id.bottomright_snapshot)!!,
-                    iconView2,
+                    R.id.bottomright_snapshot,
+                    R.id.bottomRight_icon,
+                    R.id.show_windows_right,
                     STAGE_POSITION_BOTTOM_OR_RIGHT,
-                    digitalWellBeingToast2
+                    taskOverlayFactory
                 )
             )
-        snapshotView2.bind(secondaryTask, taskOverlayFactory)
-        this.splitBoundsConfig = splitBoundsConfig
-        this.splitBoundsConfig?.let {
-            taskThumbnailViewDeprecated.previewPositionHelper.setSplitBounds(
-                convertLauncherSplitBoundsToShell(it),
-                PreviewPositionHelper.STAGE_POSITION_TOP_OR_LEFT
-            )
-            snapshotView2.previewPositionHelper.setSplitBounds(
-                convertLauncherSplitBoundsToShell(it),
-                PreviewPositionHelper.STAGE_POSITION_BOTTOM_OR_RIGHT
-            )
-        }
+        this.splitBoundsConfig =
+            splitBoundsConfig?.also {
+                taskContainers[0]
+                    .thumbnailViewDeprecated
+                    .previewPositionHelper
+                    .setSplitBounds(
+                        convertLauncherSplitBoundsToShell(it),
+                        PreviewPositionHelper.STAGE_POSITION_TOP_OR_LEFT
+                    )
+                taskContainers[1]
+                    .thumbnailViewDeprecated
+                    .previewPositionHelper
+                    .setSplitBounds(
+                        convertLauncherSplitBoundsToShell(it),
+                        PreviewPositionHelper.STAGE_POSITION_BOTTOM_OR_RIGHT
+                    )
+            }
         setOrientationState(orientedState)
     }
 
@@ -206,24 +186,18 @@
                 val iconMargins = (iconViewMarginStart + iconViewBackgroundMarginStart) * 2
                 // setMaxWidth() needs to be called before mIconView.setIconOrientation which is
                 // called in the super below.
-                (iconView as IconAppChipView).setMaxWidth(
+                (taskContainers[0].iconView as IconAppChipView).setMaxWidth(
                     groupedTaskViewSizes.first.x - iconMargins
                 )
-                (iconView2 as IconAppChipView).setMaxWidth(
+                (taskContainers[1].iconView as IconAppChipView).setMaxWidth(
                     groupedTaskViewSizes.second.x - iconMargins
                 )
             }
         }
         super.setOrientationState(orientationState)
-        iconView2.setIconOrientation(orientationState, isGridTask)
         updateIconPlacement()
     }
 
-    override fun setThumbnailOrientation(orientationState: RecentsOrientedState) {
-        super.setThumbnailOrientation(orientationState)
-        digitalWellBeingToast2.initialize(secondTask)
-    }
-
     private fun updateIconPlacement() {
         val splitBoundsConfig = splitBoundsConfig ?: return
         val taskIconHeight = container.deviceProfile.overviewTaskIconSizePx
@@ -237,8 +211,8 @@
                     layoutParams.height
                 )
             pagedOrientationHandler.setSplitIconParams(
-                iconView.asView(),
-                iconView2.asView(),
+                taskContainers[0].iconView.asView(),
+                taskContainers[1].iconView.asView(),
                 taskIconHeight,
                 groupedTaskViewSizes.first.x,
                 groupedTaskViewSizes.first.y,
@@ -250,11 +224,11 @@
             )
         } else {
             pagedOrientationHandler.setSplitIconParams(
-                iconView.asView(),
-                iconView2.asView(),
+                taskContainers[0].iconView.asView(),
+                taskContainers[1].iconView.asView(),
                 taskIconHeight,
-                taskThumbnailViewDeprecated.measuredWidth,
-                taskThumbnailViewDeprecated.measuredHeight,
+                taskContainers[0].thumbnailViewDeprecated.measuredWidth,
+                taskContainers[0].thumbnailViewDeprecated.measuredHeight,
                 measuredHeight,
                 measuredWidth,
                 isRtl,
@@ -264,104 +238,11 @@
         }
     }
 
-    override fun onTaskListVisibilityChanged(visible: Boolean, changes: Int) {
-        super.onTaskListVisibilityChanged(visible, changes)
-        val model = RecentsModel.INSTANCE.get(context)
-        if (needsUpdate(changes, FLAG_UPDATE_THUMBNAIL)) {
-            if (visible) {
-                thumbnailLoadRequest2 =
-                    model.thumbnailCache.updateThumbnailInBackground(secondTask) {
-                        snapshotView2.setThumbnail(secondTask, it)
-                    }
-            } else {
-                snapshotView2.setThumbnail(null, null)
-                // Reset the task thumbnail reference as well (it will be fetched from the cache or
-                // reloaded next time we need it)
-                secondTask.thumbnail = null
-            }
-        }
-        if (needsUpdate(changes, FLAG_UPDATE_ICON)) {
-            if (visible) {
-                iconLoadRequest2 =
-                    model.iconCache.updateIconInBackground(secondTask) {
-                        setIcon(iconView2, it.icon)
-                        if (enableOverviewIconMenu()) {
-                            setText(iconView2, it.title)
-                        }
-                        digitalWellBeingToast2.initialize(secondTask)
-                        digitalWellBeingToast2.setSplitConfiguration(splitBoundsConfig)
-                        digitalWellBeingToast.setSplitConfiguration(splitBoundsConfig)
-                    }
-            } else {
-                setIcon(iconView2, null)
-                if (enableOverviewIconMenu()) {
-                    setText(iconView2, null)
-                }
-            }
-        }
-    }
-
-    override fun cancelPendingLoadTasks() {
-        super.cancelPendingLoadTasks()
-        thumbnailLoadRequest2?.cancel()
-        thumbnailLoadRequest2 = null
-        iconLoadRequest2?.cancel()
-        iconLoadRequest2 = null
-    }
-
-    override fun getThumbnailBounds(bounds: Rect, relativeToDragLayer: Boolean) {
-        splitBoundsConfig ?: return super.getThumbnailBounds(bounds, relativeToDragLayer)
-        if (relativeToDragLayer) {
-            val firstThumbnailBounds = Rect()
-            val secondThumbnailBounds = Rect()
-            with(container.dragLayer) {
-                getDescendantRectRelativeToSelf(snapshotView, firstThumbnailBounds)
-                getDescendantRectRelativeToSelf(snapshotView2, secondThumbnailBounds)
-            }
-            bounds.set(firstThumbnailBounds)
-            bounds.union(secondThumbnailBounds)
-        } else {
-            bounds.set(getSnapshotViewBounds(snapshotView))
-            bounds.union(getSnapshotViewBounds(snapshotView2))
-        }
-    }
-
-    private fun getSnapshotViewBounds(snapshotView: View): Rect {
-        val snapshotViewX = Math.round(snapshotView.x)
-        val snapshotViewY = Math.round(snapshotView.y)
-        return Rect(
-            snapshotViewX,
-            snapshotViewY,
-            snapshotViewX + snapshotView.width,
-            snapshotViewY + snapshotView.height
-        )
-    }
-
-    /**
-     * Sets up an on-click listener and the visibility for show_windows icon on top of each task.
-     */
-    override fun setUpShowAllInstancesListener() {
-        // sets up the listener for the left/top task
-        super.setUpShowAllInstancesListener()
-        // right/bottom task's base package name
-        val taskPackageName = taskContainers[1].task.key.packageName
-        // icon of the right/bottom task
-        val showWindowsView = findViewById<View>(R.id.show_windows_right)!!
-        updateFilterCallback(showWindowsView, getFilterUpdateCallback(taskPackageName))
-    }
-
     fun updateSplitBoundsConfig(splitBounds: SplitConfigurationOptions.SplitBounds?) {
         splitBoundsConfig = splitBounds
         invalidate()
     }
 
-    override fun offerTouchToChildren(event: MotionEvent): Boolean {
-        computeAndSetIconTouchDelegate(iconView2, icon2CenterCoordinates, icon2TouchDelegate)
-        return if (icon2TouchDelegate.onTouchEvent(event)) {
-            true
-        } else super.offerTouchToChildren(event)
-    }
-
     override fun launchTaskAnimated(): RunnableList? {
         if (taskContainers.isEmpty()) {
             Log.d(TAG, "launchTaskAnimated - task is not bound")
@@ -414,12 +295,6 @@
         }
     }
 
-    override fun refreshThumbnails(thumbnailDatas: HashMap<Int, ThumbnailData?>?) {
-        super.refreshThumbnails(thumbnailDatas)
-        thumbnailDatas?.get(secondTask.key.id)?.let { snapshotView2.setThumbnail(secondTask, it) }
-            ?: { snapshotView2.refresh() }
-    }
-
     /**
      * Returns taskId that split selection was initiated with, [INVALID_TASK_ID] if no tasks in this
      * TaskView are part of split selection
@@ -437,14 +312,14 @@
             // checks below aren't reliable since both of those views may be gone/transformed
             val initSplitTaskId = getThisTaskCurrentlyInSplitSelection()
             if (initSplitTaskId != INVALID_TASK_ID) {
-                return if (initSplitTaskId == firstTask.key.id) 1 else 0
+                return if (initSplitTaskId == taskContainers[0].task.key.id) 1 else 0
             }
         }
 
         // Check which of the two apps was selected
         if (
-            iconView2.asView().containsPoint(mLastTouchDownPosition) ||
-                snapshotView2.containsPoint(mLastTouchDownPosition)
+            taskContainers[1].iconView.asView().containsPoint(lastTouchDownPosition) ||
+                taskContainers[1].thumbnailViewDeprecated.containsPoint(lastTouchDownPosition)
         ) {
             return 1
         }
@@ -457,36 +332,6 @@
         return Utilities.pointInView(this, localPos[0], localPos[1], 0f /* slop */)
     }
 
-    override fun setIconsAndBannersTransitionProgress(progress: Float, invert: Boolean) {
-        super.setIconsAndBannersTransitionProgress(progress, invert)
-        getIconContentScale(invert).let {
-            iconView2.setContentAlpha(it)
-            digitalWellBeingToast2.updateBannerOffset(1f - it)
-        }
-    }
-
-    override fun setColorTint(amount: Float, tintColor: Int) {
-        super.setColorTint(amount, tintColor)
-        iconView2.setIconColorTint(tintColor, amount)
-        snapshotView2.dimAlpha = amount
-        digitalWellBeingToast2.setBannerColorTint(tintColor, amount)
-    }
-
-    /**
-     * Sets visibility for thumbnails and associated elements (DWB banners). IconView is unaffected.
-     *
-     * When setting INVISIBLE, sets the visibility for the last selected child task. When setting
-     * VISIBLE (as a reset), sets the visibility for both tasks.
-     */
-    override fun setThumbnailVisibility(visibility: Int, taskId: Int) {
-        taskContainers.forEach {
-            if (visibility == VISIBLE || it.task.key.id == taskId) {
-                it.thumbnailView.visibility = visibility
-                it.digitalWellBeingToast?.setBannerVisibility(visibility)
-            }
-        }
-    }
-
     override fun setOverlayEnabled(overlayEnabled: Boolean) {
         if (FeatureFlags.enableAppPairs()) {
             super.setOverlayEnabled(overlayEnabled)
@@ -495,26 +340,6 @@
         }
     }
 
-    override fun refreshTaskThumbnailSplash() {
-        super.refreshTaskThumbnailSplash()
-        snapshotView2.refreshSplashView()
-    }
-
-    override fun updateSnapshotRadius() {
-        super.updateSnapshotRadius()
-        snapshotView2.setFullscreenParams(currentFullscreenParams)
-    }
-
-    override fun applyThumbnailSplashAlpha() {
-        super.applyThumbnailSplashAlpha()
-        snapshotView2.setSplashAlpha(taskThumbnailSplashAlpha)
-    }
-
-    override fun resetViewTransforms() {
-        super.resetViewTransforms()
-        snapshotView2.resetViewTransforms()
-    }
-
     companion object {
         private const val TAG = "GroupedTaskView"
     }
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 3c0445f..4e5d646 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -1006,7 +1006,8 @@
                     if (container == null || taskId != container.getTask().key.id) {
                         continue;
                     }
-                    container.getThumbnailView().setThumbnail(container.getTask(), thumbnailData);
+                    container.getThumbnailViewDeprecated().setThumbnail(container.getTask(),
+                            thumbnailData);
                 }
             }
         }
@@ -1020,8 +1021,8 @@
             Task task = tv.getFirstTask();
             if (pkg.equals(task.key.getPackageName()) && task.key.userId == user.getIdentifier()) {
                 task.icon = null;
-                TaskViewIcon firstIconView = tv.getFirstIconView();
-                if (firstIconView.getDrawable() != null) {
+                if (tv.getTaskContainers().stream().anyMatch(
+                        container -> container.getIconView().getDrawable() != null)) {
                     tv.onTaskListVisibilityChanged(true /* visible */);
                 }
             }
@@ -1058,7 +1059,7 @@
             }
             Task task = taskAttributes.getTask();
             TaskThumbnailViewDeprecated taskThumbnailViewDeprecated =
-                    taskAttributes.getThumbnailView();
+                    taskAttributes.getThumbnailViewDeprecated();
             taskThumbnailViewDeprecated.setThumbnail(task, thumbnail, refreshNow);
             // thumbnailData can contain 1-2 ids, but they should correspond to the same
             // TaskView, so overwriting is ok
@@ -3832,7 +3833,7 @@
                         announceForAccessibility(
                                 getResources().getString(R.string.task_view_closed));
                         mContainer.getStatsLogManager().logger()
-                                .withItemInfo(dismissedTaskView.getItemInfo())
+                                .withItemInfo(dismissedTaskView.getFirstItemInfo())
                                 .log(LAUNCHER_TASK_DISMISS_SWIPE_UP);
                     }
 
@@ -4743,7 +4744,8 @@
      */
     public void resetModalVisuals() {
         if (mSelectedTask != null) {
-            mSelectedTask.getFirstThumbnailView().getTaskOverlay().resetModalVisuals();
+            mSelectedTask.taskContainers.forEach(
+                    taskContainer -> taskContainer.getOverlay().resetModalVisuals());
         }
     }
 
@@ -4761,8 +4763,8 @@
     public void initiateSplitSelect(TaskView taskView, @StagePosition int stagePosition,
             StatsLogManager.EventEnum splitEvent) {
         mSplitHiddenTaskView = taskView;
-        mSplitSelectStateController.setInitialTaskSelect(null /*intent*/,
-                stagePosition, taskView.getItemInfo(), splitEvent, taskView.getFirstTask().key.id);
+        mSplitSelectStateController.setInitialTaskSelect(null /*intent*/, stagePosition,
+                taskView.getFirstItemInfo(), splitEvent, taskView.getFirstTask().key.id);
         mSplitSelectStateController.setAnimateCurrentTaskDismissal(
                 true /*animateCurrentTaskDismissal*/);
         mSplitHiddenTaskViewIndex = indexOfChild(taskView);
@@ -4815,7 +4817,7 @@
                     == mSplitSelectStateController.getInitialTaskId();
             TaskContainer taskContainer = mSplitHiddenTaskView
                     .getTaskContainers().get(primaryTaskSelected ? 1 : 0);
-            TaskThumbnailViewDeprecated thumbnail = taskContainer.getThumbnailView();
+            TaskThumbnailViewDeprecated thumbnail = taskContainer.getThumbnailViewDeprecated();
             mSplitSelectStateController.getSplitAnimationController()
                     .addInitialSplitFromPair(taskContainer, builder,
                             mContainer.getDeviceProfile(),
@@ -5215,7 +5217,7 @@
         updateGridProperties();
         updateScrollSynchronously();
 
-        int targetSysUiFlags = tv.getFirstThumbnailView().getSysUiStatusNavFlags();
+        int targetSysUiFlags = tv.getFirstThumbnailViewDeprecated().getSysUiStatusNavFlags();
         final boolean[] passedOverviewThreshold = new boolean[] {false};
         ValueAnimator progressAnim = ValueAnimator.ofFloat(0, 1);
         progressAnim.addUpdateListener(animator -> {
@@ -5279,7 +5281,7 @@
                 } else {
                     tv.launchTask(this::onTaskLaunchAnimationEnd);
                 }
-                mContainer.getStatsLogManager().logger().withItemInfo(tv.getItemInfo())
+                mContainer.getStatsLogManager().logger().withItemInfo(tv.getFirstItemInfo())
                         .log(LAUNCHER_TASK_LAUNCH_SWIPE_DOWN);
             } else {
                 onTaskLaunchAnimationEnd(false);
@@ -5917,7 +5919,7 @@
 
             ThumbnailData td =
                     mRecentsAnimationController.screenshotTask(container.getTask().key.id);
-            TaskThumbnailViewDeprecated thumbnailView = container.getThumbnailView();
+            TaskThumbnailViewDeprecated thumbnailView = container.getThumbnailViewDeprecated();
             if (td != null) {
                 thumbnailView.setThumbnail(container.getTask(), td);
             } else {
diff --git a/quickstep/src/com/android/quickstep/views/TaskMenuView.java b/quickstep/src/com/android/quickstep/views/TaskMenuView.java
index 578d471..59ffc39 100644
--- a/quickstep/src/com/android/quickstep/views/TaskMenuView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskMenuView.java
@@ -241,12 +241,13 @@
         mContainer.getDragLayer().getDescendantRectRelativeToSelf(
                 enableOverviewIconMenu()
                         ? getIconView().findViewById(R.id.icon_view_menu_anchor)
-                        : taskContainer.getThumbnailView(),
+                        : taskContainer.getThumbnailViewDeprecated(),
                 sTempRect);
         Rect insets = mContainer.getDragLayer().getInsets();
         BaseDragLayer.LayoutParams params = (BaseDragLayer.LayoutParams) getLayoutParams();
-        params.width = orientationHandler.getTaskMenuWidth(taskContainer.getThumbnailView(),
-                deviceProfile, taskContainer.getStagePosition());
+        params.width = orientationHandler.getTaskMenuWidth(
+                taskContainer.getThumbnailViewDeprecated(), deviceProfile,
+                taskContainer.getStagePosition());
         // Gravity set to Left instead of Start as sTempRect.left measures Left distance not Start
         params.gravity = Gravity.LEFT;
         setLayoutParams(params);
@@ -279,10 +280,10 @@
             // Margin that insets the menuView inside the taskView
             float taskInsetMargin = getResources().getDimension(R.dimen.task_card_margin);
             setTranslationX(orientationHandler.getTaskMenuX(thumbnailAlignedX,
-                    mTaskContainer.getThumbnailView(), deviceProfile, taskInsetMargin,
+                    mTaskContainer.getThumbnailViewDeprecated(), deviceProfile, taskInsetMargin,
                     getIconView()));
             setTranslationY(orientationHandler.getTaskMenuY(
-                    thumbnailAlignedY, mTaskContainer.getThumbnailView(),
+                    thumbnailAlignedY, mTaskContainer.getThumbnailViewDeprecated(),
                     mTaskContainer.getStagePosition(), this, taskInsetMargin,
                     getIconView()));
         }
@@ -367,7 +368,7 @@
         }
         mOpenCloseAnimator.playTogether(mRevealAnimator,
                 ObjectAnimator.ofFloat(
-                        mTaskContainer.getThumbnailView(), DIM_ALPHA,
+                        mTaskContainer.getThumbnailViewDeprecated(), DIM_ALPHA,
                         closing ? 0 : TaskView.MAX_PAGE_SCRIM_ALPHA),
                 ObjectAnimator.ofFloat(this, ALPHA, closing ? 0 : 1));
         mOpenCloseAnimator.addListener(new AnimationSuccessListener() {
diff --git a/quickstep/src/com/android/quickstep/views/TaskThumbnailViewDeprecated.java b/quickstep/src/com/android/quickstep/views/TaskThumbnailViewDeprecated.java
index fed6af5..447002f 100644
--- a/quickstep/src/com/android/quickstep/views/TaskThumbnailViewDeprecated.java
+++ b/quickstep/src/com/android/quickstep/views/TaskThumbnailViewDeprecated.java
@@ -54,7 +54,6 @@
 import com.android.launcher3.util.SystemUiController;
 import com.android.launcher3.util.SystemUiController.SystemUiControllerFlags;
 import com.android.launcher3.util.ViewPool;
-import com.android.quickstep.TaskOverlayFactory;
 import com.android.quickstep.TaskOverlayFactory.TaskOverlay;
 import com.android.quickstep.orientation.RecentsPagedOrientationHandler;
 import com.android.quickstep.views.TaskView.FullscreenDrawParams;
@@ -129,9 +128,7 @@
             };
 
     private final RecentsViewContainer mContainer;
-    private TaskOverlayFactory mTaskOverlayFactory;
-    @Nullable
-    private TaskOverlay mOverlay;
+    private TaskOverlay<?> mOverlay;
     private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
     private final Paint mBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
     private final Paint mSplashBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
@@ -190,9 +187,9 @@
     /**
      * Updates the thumbnail to draw the provided task
      */
-    public void bind(Task task, TaskOverlayFactory taskOverlayFactory) {
-        mTaskOverlayFactory = taskOverlayFactory;
-        getTaskOverlay().reset();
+    public void bind(Task task, TaskOverlay<?> overlay) {
+        mOverlay = overlay;
+        mOverlay.reset();
         mTask = task;
         int color = task == null ? Color.BLACK : task.colorBackground | 0xFF000000;
         mPaint.setColor(color);
@@ -202,14 +199,14 @@
     }
 
     /**
-     * Sets TaskOverlayFactory without binding a task.
+     * Sets TaskOverlay without binding a task.
      *
      * @deprecated Should only be used when using new
      * {@link com.android.quickstep.task.thumbnail.TaskThumbnailView}.
      */
     @Deprecated
-    public void setTaskOverlayFactory(TaskOverlayFactory taskOverlayFactory) {
-        mTaskOverlayFactory = taskOverlayFactory;
+    public void setTaskOverlay(TaskOverlay<?> overlay) {
+        mOverlay = overlay;
     }
 
     /**
@@ -265,7 +262,7 @@
             mBitmapShader = null;
             mThumbnailData = null;
             mPaint.setShader(null);
-            getTaskOverlay().reset();
+            mOverlay.reset();
         }
         updateThumbnailPaintFilter();
     }
@@ -293,13 +290,6 @@
         invalidate();
     }
 
-    public TaskOverlay getTaskOverlay() {
-        if (mOverlay == null) {
-            mOverlay = mTaskOverlayFactory.createOverlay(this);
-        }
-        return mOverlay;
-    }
-
     public float getDimAlpha() {
         return mDimAlpha;
     }
@@ -374,7 +364,6 @@
 
     public void setFullscreenParams(TaskView.FullscreenDrawParams fullscreenParams) {
         mFullscreenParams = fullscreenParams;
-        getTaskOverlay().setFullscreenParams(fullscreenParams);
         invalidate();
     }
 
@@ -551,10 +540,10 @@
      */
     private void refreshOverlay() {
         if (mOverlayEnabled) {
-            getTaskOverlay().initOverlay(mTask, mThumbnailData, mPreviewPositionHelper.getMatrix(),
+            mOverlay.initOverlay(mTask, mThumbnailData, mPreviewPositionHelper.getMatrix(),
                     mPreviewPositionHelper.isOrientationChanged());
         } else {
-            getTaskOverlay().reset();
+            mOverlay.reset();
         }
     }
 
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.kt b/quickstep/src/com/android/quickstep/views/TaskView.kt
index baeaa87..1490fd0 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskView.kt
@@ -42,7 +42,6 @@
 import android.widget.Toast
 import androidx.annotation.IntDef
 import androidx.annotation.VisibleForTesting
-import androidx.core.view.children
 import androidx.core.view.updateLayoutParams
 import com.android.app.animation.Interpolators
 import com.android.launcher3.Flags.enableCursorHoverStates
@@ -57,6 +56,7 @@
 import com.android.launcher3.Utilities
 import com.android.launcher3.config.FeatureFlags.ENABLE_KEYBOARD_QUICK_SWITCH
 import com.android.launcher3.logging.StatsLogManager.LauncherEvent
+import com.android.launcher3.model.data.ItemInfo
 import com.android.launcher3.model.data.ItemInfoWithIcon
 import com.android.launcher3.model.data.WorkspaceItemInfo
 import com.android.launcher3.pm.UserCache
@@ -79,6 +79,7 @@
 import com.android.quickstep.RemoteAnimationTargets
 import com.android.quickstep.TaskAnimationManager
 import com.android.quickstep.TaskOverlayFactory
+import com.android.quickstep.TaskOverlayFactory.TaskOverlay
 import com.android.quickstep.TaskUtils
 import com.android.quickstep.TaskViewUtils
 import com.android.quickstep.orientation.RecentsPagedOrientationHandler
@@ -132,7 +133,7 @@
         /** Returns a copy of integer array containing taskIds of all tasks in the TaskView. */
         get() = taskContainers.map { it.task.key.id }.toIntArray()
     val thumbnailViews: Array<TaskThumbnailViewDeprecated>
-        get() = taskContainers.map { it.thumbnailView }.toTypedArray()
+        get() = taskContainers.map { it.thumbnailViewDeprecated }.toTypedArray()
     val isGridTask: Boolean
         /** Returns whether the task is part of overview grid and not being focused. */
         get() = container.deviceProfile.isTablet && !isFocusedTask
@@ -147,27 +148,22 @@
     val pagedOrientationHandler: RecentsPagedOrientationHandler
         get() = orientedState.orientationHandler
 
-    @get:Deprecated("Use {@link #mTaskContainers} instead.")
+    @get:Deprecated("Use [taskContainers] instead.")
     val firstTask: Task
         /** Returns the first task bound to this TaskView. */
         get() = taskContainers[0].task
-    @get:Deprecated("Use {@link #mTaskContainers} instead.")
-    val firstThumbnailView: TaskThumbnailViewDeprecated
+    @get:Deprecated("Use [taskContainers] instead.")
+    val firstThumbnailViewDeprecated: TaskThumbnailViewDeprecated
         /** Returns the first thumbnailView of the TaskView. */
-        get() = taskContainers[0].thumbnailView
-    @get:Deprecated("Use {@link #mTaskContainers} instead.")
-    val firstIconView: TaskViewIcon
-        /** Returns the first iconView of the TaskView. */
-        get() = taskContainers[0].iconView
-    protected val currentFullscreenParams = FullscreenDrawParams(context)
+        get() = taskContainers[0].thumbnailViewDeprecated
+    @get:Deprecated("Use [taskContainers] instead.")
+    val firstItemInfo: ItemInfo
+        get() = taskContainers[0].itemInfo
+
+    private val currentFullscreenParams = FullscreenDrawParams(context)
     protected val container: RecentsViewContainer =
         RecentsViewContainer.containerFromContext(context)
-    @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
-    val digitalWellBeingToast = DigitalWellBeingToast(container, this)
-    protected val mLastTouchDownPosition = PointF()
-    protected val snapshotView: View
-        get() =
-            if (enableRefactorTaskThumbnail()) taskThumbnailView!! else taskThumbnailViewDeprecated
+    protected val lastTouchDownPosition = PointF()
 
     // Derived view properties
     protected val persistentScale: Float
@@ -228,9 +224,9 @@
                 TASK_RESISTANCE_TRANSLATION_Y
             )
 
-    private val mIconCenterCoordinates = FloatArray(2)
-    private val mFocusBorderAnimator: BorderAnimator?
-    private val mHoverBorderAnimator: BorderAnimator?
+    private val tempCoordinates = FloatArray(2)
+    private val focusBorderAnimator: BorderAnimator?
+    private val hoverBorderAnimator: BorderAnimator?
     private val rootViewDisplayId: Int
         get() = rootView.display?.displayId ?: Display.DEFAULT_DISPLAY
 
@@ -238,16 +234,6 @@
     lateinit var taskContainers: List<TaskContainer>
         protected set
     lateinit var orientedState: RecentsOrientedState
-    protected lateinit var taskThumbnailViewDeprecated: TaskThumbnailViewDeprecated
-    protected lateinit var iconView: TaskViewIcon
-    /**
-     * This technically can be a vanilla [android.view.TouchDelegate] class, however that class
-     * requires setting the touch bounds at construction, so we'd repeatedly be created many
-     * instances unnecessarily as scrolling occurs, whereas [TransformingTouchDelegate] allows touch
-     * delegated bounds only to be updated.
-     */
-    private lateinit var iconTouchDelegate: TransformingTouchDelegate
-    private var taskThumbnailView: TaskThumbnailView? = null
 
     var taskViewId = -1
     var isEndQuickSwitchCuj = false
@@ -382,26 +368,26 @@
             field = value
             // Set the animation correctly in case it misses the hover/focus event during state
             // transition
-            mHoverBorderAnimator?.setBorderVisibility(visible = field && isHovered, animated = true)
-            mFocusBorderAnimator?.setBorderVisibility(visible = field && isFocused, animated = true)
+            hoverBorderAnimator?.setBorderVisibility(visible = field && isHovered, animated = true)
+            focusBorderAnimator?.setBorderVisibility(visible = field && isFocused, animated = true)
         }
     protected var iconScaleAnimStartProgress = 0f
     private var focusTransitionProgress = 1f
 
     private var iconAndDimAnimator: ObjectAnimator? = null
     // The current background requests to load the task thumbnail and icon
-    private var thumbnailLoadRequest: CancellableTask<*>? = null
-    private var iconLoadRequest: CancellableTask<*>? = null
+    private val pendingThumbnailLoadRequests = mutableListOf<CancellableTask<*>>()
+    private val pendingIconLoadRequests = mutableListOf<CancellableTask<*>>()
     private var isClickableAsLiveTile = true
 
     init {
-        setOnClickListener { view: View -> onClick(view) }
+        setOnClickListener { _ -> onClick() }
         val keyboardFocusHighlightEnabled =
             (ENABLE_KEYBOARD_QUICK_SWITCH.get() || enableFocusOutline())
         val cursorHoverStatesEnabled = enableCursorHoverStates()
         setWillNotDraw(!keyboardFocusHighlightEnabled && !cursorHoverStatesEnabled)
         context.obtainStyledAttributes(attrs, R.styleable.TaskView, defStyleAttr, defStyleRes).use {
-            mFocusBorderAnimator =
+            this.focusBorderAnimator =
                 focusBorderAnimator
                     ?: if (keyboardFocusHighlightEnabled)
                         createSimpleBorderAnimator(
@@ -417,7 +403,7 @@
                             )
                         )
                     else null
-            mHoverBorderAnimator =
+            this.hoverBorderAnimator =
                 hoverBorderAnimator
                     ?: if (cursorHoverStatesEnabled)
                         createSimpleBorderAnimator(
@@ -436,28 +422,6 @@
         }
     }
 
-    override fun onFinishInflate() {
-        super.onFinishInflate()
-        taskThumbnailViewDeprecated = findViewById(R.id.snapshot)!!
-        if (enableRefactorTaskThumbnail()) {
-            val indexOfSnapshotView = indexOfChild(taskThumbnailViewDeprecated)
-            taskThumbnailView =
-                TaskThumbnailView(mContext).apply {
-                    layoutParams = taskThumbnailViewDeprecated.layoutParams
-                    addView(this, indexOfSnapshotView)
-                }
-            taskThumbnailViewDeprecated.visibility = GONE
-        }
-        val iconViewStub =
-            findViewById<ViewStub>(R.id.icon)!!.apply {
-                layoutResource =
-                    if (enableOverviewIconMenu()) R.layout.icon_app_chip_view
-                    else R.layout.icon_view
-            }
-        iconView = iconViewStub.inflate() as TaskViewIcon
-        iconTouchDelegate = TransformingTouchDelegate(iconView.asView())
-    }
-
     @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
     public override fun onFocusChanged(
         gainFocus: Boolean,
@@ -466,7 +430,7 @@
     ) {
         super.onFocusChanged(gainFocus, direction, previouslyFocusedRect)
         if (borderEnabled) {
-            mFocusBorderAnimator?.setBorderVisibility(gainFocus, /* animated= */ true)
+            focusBorderAnimator?.setBorderVisibility(gainFocus, /* animated= */ true)
         }
     }
 
@@ -474,9 +438,9 @@
         if (borderEnabled) {
             when (event.action) {
                 MotionEvent.ACTION_HOVER_ENTER ->
-                    mHoverBorderAnimator?.setBorderVisibility(visible = true, animated = true)
+                    hoverBorderAnimator?.setBorderVisibility(visible = true, animated = true)
                 MotionEvent.ACTION_HOVER_EXIT ->
-                    mHoverBorderAnimator?.setBorderVisibility(visible = false, animated = true)
+                    hoverBorderAnimator?.setBorderVisibility(visible = false, animated = true)
                 else -> {}
             }
         }
@@ -491,16 +455,15 @@
     override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
         val recentsView = recentsView ?: return false
         val splitSelectStateController = recentsView.splitSelectController
-        // Disable taps for split selection animation unless we have multiple tasks
+        // Disable taps for split selection animation unless we have a task not being selected
         if (
             splitSelectStateController.isSplitSelectActive &&
-                splitSelectStateController.initialTaskId == firstTask.key.id &&
-                !containsMultipleTasks()
+                taskContainers.none { it.task.key.id != splitSelectStateController.initialTaskId }
         ) {
             return false
         }
         if (ev.action == MotionEvent.ACTION_DOWN) {
-            mLastTouchDownPosition.apply {
+            with(lastTouchDownPosition) {
                 x = ev.x
                 y = ev.y
             }
@@ -510,8 +473,8 @@
 
     override fun draw(canvas: Canvas) {
         // Draw border first so any child views outside of the thumbnail bounds are drawn above it.
-        mFocusBorderAnimator?.drawBorder(canvas)
-        mHoverBorderAnimator?.drawBorder(canvas)
+        focusBorderAnimator?.drawBorder(canvas)
+        hoverBorderAnimator?.drawBorder(canvas)
         super.draw(canvas)
     }
 
@@ -540,7 +503,7 @@
         if (enableRefactorTaskThumbnail()) {
             notifyIsRunningTaskUpdated()
         } else {
-            taskThumbnailViewDeprecated.setThumbnail(firstTask, null)
+            taskContainers.forEach { it.thumbnailViewDeprecated.setThumbnail(it.task, null) }
         }
         setOverlayEnabled(false)
         onTaskListVisibilityChanged(false)
@@ -566,7 +529,8 @@
                     }
                 }
             }
-            if (digitalWellBeingToast.hasLimit()) {
+            // TODO(b/341672022): handle multiple digitalWellBeingToast accessibility actions
+            if (taskContainers[0].digitalWellBeingToast?.hasLimit() == true) {
                 addAction(
                     AccessibilityNodeInfo.AccessibilityAction(
                         R.string.accessibility_app_usage_settings,
@@ -593,7 +557,8 @@
             return true
         }
         if (action == R.string.accessibility_app_usage_settings) {
-            digitalWellBeingToast.openAppUsageSettings(this)
+            // TODO(b/341672022): handle multiple digitalWellBeingToast accessibility actions
+            taskContainers[0].digitalWellBeingToast?.openAppUsageSettings(this)
             return true
         }
         taskContainers.forEach {
@@ -614,27 +579,73 @@
         taskOverlayFactory: TaskOverlayFactory
     ) {
         cancelPendingLoadTasks()
-        setupTaskContainers(task, taskOverlayFactory)
+        taskContainers =
+            listOf(
+                createTaskContainer(
+                    task,
+                    R.id.snapshot,
+                    R.id.icon,
+                    R.id.show_windows,
+                    STAGE_POSITION_UNDEFINED,
+                    taskOverlayFactory
+                )
+            )
         setOrientationState(orientedState)
     }
 
-    protected fun setupTaskContainers(task: Task, taskOverlayFactory: TaskOverlayFactory) {
-        taskContainers =
-            listOf(
-                TaskContainer(
-                    task,
-                    taskThumbnailViewDeprecated,
-                    iconView,
-                    STAGE_POSITION_UNDEFINED,
-                    digitalWellBeingToast
-                )
-            )
+    protected fun createTaskContainer(
+        task: Task,
+        @IdRes thumbnailViewId: Int,
+        @IdRes iconViewId: Int,
+        @IdRes showWindowViewId: Int,
+        @StagePosition stagePosition: Int,
+        taskOverlayFactory: TaskOverlayFactory
+    ): TaskContainer {
+        val thumbnailViewDeprecated: TaskThumbnailViewDeprecated = findViewById(thumbnailViewId)!!
+        val thumbnailView: TaskThumbnailView?
         if (enableRefactorTaskThumbnail()) {
-            taskThumbnailViewDeprecated.setTaskOverlayFactory(taskOverlayFactory)
-            bindTaskThumbnailView()
+            val indexOfSnapshotView = indexOfChild(thumbnailViewDeprecated)
+            thumbnailView =
+                TaskThumbnailView(context).apply {
+                    layoutParams = thumbnailViewDeprecated.layoutParams
+                    addView(this, indexOfSnapshotView)
+                }
+            thumbnailViewDeprecated.visibility = GONE
         } else {
-            taskThumbnailViewDeprecated.bind(task, taskOverlayFactory)
+            thumbnailView = null
         }
+        val iconView = getOrInflateIconView(iconViewId)
+        return TaskContainer(
+                task,
+                thumbnailView,
+                thumbnailViewDeprecated,
+                iconView,
+                TransformingTouchDelegate(iconView.asView()),
+                stagePosition,
+                DigitalWellBeingToast(container, this),
+                findViewById(showWindowViewId)!!,
+                taskOverlayFactory
+            )
+            .apply {
+                if (enableRefactorTaskThumbnail()) {
+                    thumbnailViewDeprecated.setTaskOverlay(overlay)
+                    bindThumbnailView()
+                } else {
+                    thumbnailViewDeprecated.bind(task, overlay)
+                }
+            }
+    }
+
+    protected fun getOrInflateIconView(@IdRes iconViewId: Int): TaskViewIcon {
+        val iconView = findViewById<View>(iconViewId)!!
+        return iconView as? TaskViewIcon
+            ?: (iconView as ViewStub)
+                .apply {
+                    layoutResource =
+                        if (enableOverviewIconMenu()) R.layout.icon_app_chip_view
+                        else R.layout.icon_view
+                }
+                .inflate() as TaskViewIcon
     }
 
     protected fun isTaskContainersInitialized() = this::taskContainers.isInitialized
@@ -650,31 +661,17 @@
     /** Check if given `taskId` is tracked in this view */
     fun containsTaskId(taskId: Int) = getTaskContainerById(taskId) != null
 
-    // TODO(b/335649589): TaskView's VM will already have access to TaskThumbnailView's VM
-    //  so there will be no need to access TaskThumbnailView's VM through the TaskThumbnailView
-    private fun bindTaskThumbnailView() {
-        taskThumbnailView!!.viewModel.bind(TaskThumbnail(firstTask, isRunningTask))
-    }
-
     open fun setOrientationState(orientationState: RecentsOrientedState) {
         this.orientedState = orientationState
-        iconView.setIconOrientation(orientationState, isGridTask)
+        taskContainers.forEach { it.iconView.setIconOrientation(orientationState, isGridTask) }
         setThumbnailOrientation(orientationState)
     }
 
     protected open fun setThumbnailOrientation(orientationState: RecentsOrientedState) {
-        val thumbnailTopMargin = container.deviceProfile.overviewTaskThumbnailTopMarginPx
-
-        // TODO(b/271468547), we should default to setting translations only on the snapshot instead
-        //  of a hybrid of both margins and translations
-        snapshotView.updateLayoutParams<LayoutParams> { topMargin = thumbnailTopMargin }
-
-        // TODO(b/335606129) Investigate the usage of [TaskOverlay] in the new TaskThumbnailView.
-        //  and if it's still necessary we should support that in the new TTV class.
-        if (!enableRefactorTaskThumbnail()) {
-            taskThumbnailViewDeprecated.taskOverlay.updateOrientationState(orientationState)
+        taskContainers.forEach {
+            it.overlay.updateOrientationState(orientationState)
+            it.digitalWellBeingToast?.initialize(it.task)
         }
-        digitalWellBeingToast.initialize(firstTask)
     }
 
     /**
@@ -696,7 +693,6 @@
         if (container.deviceProfile.isTablet) {
             val boxWidth: Int
             val boxHeight: Int
-            val isFocusedTask = isFocusedTask
             if (isFocusedTask) {
                 // Task will be focused and should use focused task size. Use focusTaskRatio
                 // that is associated with the original orientation of the focused task.
@@ -736,19 +732,29 @@
             width = expectedWidth
             height = expectedHeight
         }
+        updateThumbnailSize()
+    }
+
+    protected open fun updateThumbnailSize() {
+        // TODO(b/271468547), we should default to setting translations only on the snapshot instead
+        //  of a hybrid of both margins and translations
+        taskContainers[0].snapshotView.updateLayoutParams<LayoutParams> {
+            topMargin = container.deviceProfile.overviewTaskThumbnailTopMarginPx
+        }
     }
 
     /** Returns the thumbnail's bounds, optionally relative to the screen. */
     @JvmOverloads
     open fun getThumbnailBounds(bounds: Rect, relativeToDragLayer: Boolean = false) {
-        val snapshotView = snapshotView
-        if (relativeToDragLayer) {
-            container.dragLayer.getDescendantRectRelativeToSelf(snapshotView, bounds)
-        } else {
-            bounds.apply {
-                set(snapshotView)
-                offset(Math.round(snapshotView.translationX), Math.round(snapshotView.translationY))
+        bounds.setEmpty()
+        taskContainers.forEach {
+            val thumbnailBounds = Rect()
+            if (relativeToDragLayer) {
+                container.dragLayer.getDescendantRectRelativeToSelf(it.snapshotView, bounds)
+            } else {
+                thumbnailBounds.set(it.snapshotView)
             }
+            bounds.union(thumbnailBounds)
         }
     }
 
@@ -768,42 +774,45 @@
      */
     open fun onTaskListVisibilityChanged(visible: Boolean, @TaskDataChanges changes: Int) {
         cancelPendingLoadTasks()
-        val model = RecentsModel.INSTANCE.get(context)
+        val recentsModel = RecentsModel.INSTANCE.get(context)
         // These calls are no-ops if the data is already loaded, try and load the high
         // resolution thumbnail if the state permits
         if (needsUpdate(changes, FLAG_UPDATE_THUMBNAIL)) {
-            if (visible) {
-                thumbnailLoadRequest =
-                    model.thumbnailCache.updateThumbnailInBackground(firstTask) {
-                        if (!enableRefactorTaskThumbnail()) {
-                            // TODO(b/334825222) add thumbnail state
-                            taskThumbnailViewDeprecated.setThumbnail(firstTask, it)
-                        }
+            if (!enableRefactorTaskThumbnail()) {
+                // TODO(b/334825222) add thumbnail state
+                taskContainers.forEach {
+                    if (visible) {
+                        recentsModel.thumbnailCache
+                            .updateThumbnailInBackground(it.task) { thumbnailData ->
+                                it.thumbnailViewDeprecated.setThumbnail(it.task, thumbnailData)
+                            }
+                            ?.also { request -> pendingThumbnailLoadRequests.add(request) }
+                    } else {
+                        it.thumbnailViewDeprecated.setThumbnail(null, null)
+                        // Reset the task thumbnail reference as well (it will be fetched from the
+                        // cache or reloaded next time we need it)
+                        it.task.thumbnail = null
                     }
-            } else {
-                if (!enableRefactorTaskThumbnail()) {
-                    // TODO(b/334825222) add thumbnail state
-                    taskThumbnailViewDeprecated.setThumbnail(null, null)
                 }
-                // Reset the task thumbnail reference as well (it will be fetched from the cache or
-                // reloaded next time we need it)
-                firstTask.thumbnail = null
             }
         }
         if (needsUpdate(changes, FLAG_UPDATE_ICON)) {
-            if (visible) {
-                iconLoadRequest =
-                    model.iconCache.updateIconInBackground(firstTask) {
-                        setIcon(iconView, it.icon)
-                        if (enableOverviewIconMenu()) {
-                            setText(iconView, it.title)
+            taskContainers.forEach {
+                if (visible) {
+                    recentsModel.iconCache
+                        .updateIconInBackground(it.task) { thumbnailData ->
+                            setIcon(it.iconView, thumbnailData.icon)
+                            if (enableOverviewIconMenu()) {
+                                setText(it.iconView, thumbnailData.title)
+                            }
+                            it.digitalWellBeingToast?.initialize(thumbnailData)
                         }
-                        digitalWellBeingToast.initialize(it)
+                        ?.also { request -> pendingIconLoadRequests.add(request) }
+                } else {
+                    setIcon(it.iconView, null)
+                    if (enableOverviewIconMenu()) {
+                        setText(it.iconView, null)
                     }
-            } else {
-                setIcon(iconView, null)
-                if (enableOverviewIconMenu()) {
-                    setText(iconView, null)
                 }
             }
         }
@@ -812,14 +821,14 @@
         }
     }
 
-    protected fun needsUpdate(@TaskDataChanges dataChange: Int, @TaskDataChanges flag: Int) =
+    protected open fun needsUpdate(@TaskDataChanges dataChange: Int, @TaskDataChanges flag: Int) =
         (dataChange and flag) == flag
 
     protected open fun cancelPendingLoadTasks() {
-        thumbnailLoadRequest?.cancel()
-        thumbnailLoadRequest = null
-        iconLoadRequest?.cancel()
-        iconLoadRequest = null
+        pendingThumbnailLoadRequests.forEach { it.cancel() }
+        pendingThumbnailLoadRequests.clear()
+        pendingIconLoadRequests.forEach { it.cancel() }
+        pendingIconLoadRequests.clear()
     }
 
     protected fun setIcon(iconView: TaskViewIcon, icon: Drawable?) {
@@ -852,13 +861,18 @@
             // TODO(b/334825222) add thumbnail logic
             return
         }
-        thumbnailDatas?.get(firstTask.key.id)?.let {
-            taskThumbnailViewDeprecated.setThumbnail(firstTask, it)
+
+        taskContainers.forEach {
+            val thumbnailData = thumbnailDatas?.get(it.task.key.id)
+            if (thumbnailData != null) {
+                it.thumbnailViewDeprecated.setThumbnail(it.task, thumbnailData)
+            } else {
+                it.thumbnailViewDeprecated.refresh()
+            }
         }
-            ?: { taskThumbnailViewDeprecated.refresh() }
     }
 
-    private fun onClick(view: View) {
+    private fun onClick() {
         if (confirmSecondSplitSelectApp()) {
             Log.d("b/310064698", "${taskIds.contentToString()} - onClick - split select is active")
             return
@@ -872,7 +886,7 @@
         Log.d("b/310064698", "${taskIds.contentToString()} - onClick - callbackList: $callbackList")
         container.statsLogManager
             .logger()
-            .withItemInfo(getItemInfo())
+            .withItemInfo(firstItemInfo)
             .log(LauncherEvent.LAUNCHER_TASK_LAUNCH_TAP)
     }
 
@@ -893,7 +907,7 @@
             }
         if (
             ActivityManagerWrapper.getInstance()
-                .startActivityFromRecents(firstTask.key, opts.options)
+                .startActivityFromRecents(taskContainers[0].task.key, opts.options)
         ) {
             Log.d(
                 TAG,
@@ -937,12 +951,13 @@
             "startActivityFromRecentsAsync",
             taskIds.contentToString()
         )
+        val firstContainer = taskContainers[0]
         val failureListener = TaskRemovedDuringLaunchListener(context.applicationContext)
         if (isQuickSwitch) {
             // We only listen for failures to launch in quickswitch because the during this
             // gesture launcher is in the background state, vs other launches which are in
             // the actual overview state
-            failureListener.register(container, firstTask.key.id) {
+            failureListener.register(container, firstContainer.task.key.id) {
                 notifyTaskLaunchFailed()
                 recentsView?.let {
                     // Disable animations for now, as it is an edge case and the app usually
@@ -976,12 +991,14 @@
                     }
                     // TODO(b/334826842) add splash functionality to new TTV
                     if (!enableRefactorTaskThumbnail()) {
-                        disableStartingWindow = taskThumbnailViewDeprecated.shouldShowSplashView()
+                        disableStartingWindow =
+                            firstContainer.thumbnailViewDeprecated.shouldShowSplashView()
                     }
                 }
         Executors.UI_HELPER_EXECUTOR.execute {
             if (
-                !ActivityManagerWrapper.getInstance().startActivityFromRecents(firstTask.key, opts)
+                !ActivityManagerWrapper.getInstance()
+                    .startActivityFromRecents(firstContainer.task.key, opts)
             ) {
                 // If the call to start activity failed, then post the result immediately,
                 // otherwise, wait for the animation start callback from the activity options
@@ -1039,7 +1056,7 @@
             return runnableList
         }
         val runnableList = RunnableList()
-        AnimatorSet().apply {
+        with(AnimatorSet()) {
             TaskViewUtils.composeRecentsLaunchAnimator(
                 this,
                 this@TaskView,
@@ -1054,7 +1071,7 @@
             addListener(
                 object : AnimatorListenerAdapter() {
                     override fun onAnimationEnd(animator: Animator) {
-                        if (firstTask.key.displayId != rootViewDisplayId) {
+                        if (taskContainers.any { it.task.key.displayId != rootViewDisplayId }) {
                             launchTaskAnimated()
                         }
                         isClickableAsLiveTile = true
@@ -1078,10 +1095,11 @@
     }
 
     private fun notifyTaskLaunchFailed() {
-        Log.w(
-            TAG,
-            "Failed to launch task (task=${firstTask.key.baseIntent} userId=${firstTask.key.userId})"
-        )
+        val sb = StringBuilder("Failed to launch task \n")
+        taskContainers.forEach {
+            sb.append("(task=${it.task.key.baseIntent} userId=${it.task.key.userId})\n")
+        }
+        Log.w(TAG, sb.toString())
         Toast.makeText(context, R.string.activity_not_available, Toast.LENGTH_SHORT).show()
     }
 
@@ -1108,8 +1126,8 @@
             this,
             container.task,
             container.iconView.drawable,
-            container.thumbnailView,
-            container.thumbnailView.thumbnail, /* intent */
+            container.thumbnailViewDeprecated,
+            container.thumbnailViewDeprecated.thumbnail, /* intent */
             null, /* user */
             null,
             container.itemInfo
@@ -1172,41 +1190,23 @@
         }
     }
 
-    @Deprecated("Use {@link #getItemInfo(Task)} instead.")
-    /** Builds proto for logging. */
-    fun getItemInfo() = getItemInfo(firstTask)
-
-    /** Builds proto for logging */
-    @VisibleForTesting
-    fun getItemInfo(task: Task): WorkspaceItemInfo {
-        return WorkspaceItemInfo().apply {
-            itemType = LauncherSettings.Favorites.ITEM_TYPE_TASK
-            container = LauncherSettings.Favorites.CONTAINER_TASKSWITCHER
-            val componentKey = TaskUtils.getLaunchComponentKeyForTask(task.key)
-            user = componentKey.user
-            intent = Intent().setComponent(componentKey.componentName)
-            title = task.title
-            recentsView?.let { screenId = it.indexOfChild(this@TaskView) }
-            if (privateSpaceRestrictAccessibilityDrag()) {
-                if (UserCache.getInstance(context).getUserInfo(componentKey.user).isPrivate) {
-                    runtimeStatusFlags = runtimeStatusFlags or ItemInfoWithIcon.FLAG_NOT_PINNABLE
-                }
-            }
-        }
-    }
-
     /**
      * Whether the taskview should take the touch event from parent. Events passed to children that
      * might require special handling.
      */
     open fun offerTouchToChildren(event: MotionEvent): Boolean {
-        if (event.action == MotionEvent.ACTION_DOWN) {
-            computeAndSetIconTouchDelegate(iconView, mIconCenterCoordinates, iconTouchDelegate)
+        taskContainers.forEach {
+            if (event.action == MotionEvent.ACTION_DOWN) {
+                computeAndSetIconTouchDelegate(it.iconView, tempCoordinates, it.iconTouchDelegate)
+                if (it.iconTouchDelegate.onTouchEvent(event)) {
+                    return true
+                }
+            }
         }
-        return iconTouchDelegate.onTouchEvent(event)
+        return false
     }
 
-    protected fun computeAndSetIconTouchDelegate(
+    private fun computeAndSetIconTouchDelegate(
         view: TaskViewIcon,
         tempCenterCoordinates: FloatArray,
         transformingTouchDelegate: TransformingTouchDelegate
@@ -1232,10 +1232,14 @@
 
     /** Sets up an on-click listener and the visibility for show_windows icon on top of the task. */
     open fun setUpShowAllInstancesListener() {
-        val taskPackageName = taskContainers[0].task.key.packageName
-        // icon of the top/left task
-        val showWindowsView = findViewById<View>(R.id.show_windows)!!
-        updateFilterCallback(showWindowsView, getFilterUpdateCallback(taskPackageName))
+        taskContainers.forEach {
+            it.showWindowsView?.let { showWindowsView ->
+                updateFilterCallback(
+                    showWindowsView,
+                    getFilterUpdateCallback(it.task.key.packageName)
+                )
+            }
+        }
     }
 
     /**
@@ -1243,7 +1247,7 @@
      *
      * @param taskPackageName package name of the task to filter by
      */
-    protected fun getFilterUpdateCallback(taskPackageName: String?) =
+    private fun getFilterUpdateCallback(taskPackageName: String?) =
         if (recentsView?.filterState?.shouldShowFilterUI(taskPackageName) == true)
             OnClickListener { recentsView?.setAndApplyFilter(taskPackageName) }
         else null
@@ -1252,11 +1256,13 @@
      * Sets the correct visibility and callback on the provided filterView based on whether the
      * callback is null or not
      */
-    protected fun updateFilterCallback(filterView: View, callback: OnClickListener?) {
+    private fun updateFilterCallback(filterView: View, callback: OnClickListener?) {
         // Filtering changes alpha instead of the visibility since visibility
         // can be altered separately through RecentsView#resetFromSplitSelectionState()
-        filterView.alpha = if (callback == null) 0f else 1f
-        filterView.setOnClickListener(callback)
+        with(filterView) {
+            alpha = if (callback == null) 0f else 1f
+            setOnClickListener(callback)
+        }
     }
 
     protected open fun setIconsAndBannersFullscreenProgress(progress: Float) {
@@ -1275,12 +1281,14 @@
     protected open fun setIconsAndBannersTransitionProgress(progress: Float, invert: Boolean) {
         focusTransitionProgress = if (invert) 1 - progress else progress
         getIconContentScale(invert).let { iconContentScale ->
-            iconView.setContentAlpha(iconContentScale)
-            digitalWellBeingToast.updateBannerOffset(1f - iconContentScale)
+            taskContainers.forEach {
+                it.iconView.setContentAlpha(iconContentScale)
+                it.digitalWellBeingToast?.updateBannerOffset(1f - iconContentScale)
+            }
         }
     }
 
-    protected fun getIconContentScale(invert: Boolean): Float {
+    private fun getIconContentScale(invert: Boolean): Float {
         val iconScalePercentage = SCALE_ICON_DURATION.toFloat() / DIM_ANIM_DURATION
         val lowerClamp = if (invert) 1f - iconScalePercentage else 0f
         val upperClamp = if (invert) 1f else iconScalePercentage
@@ -1312,12 +1320,14 @@
 
     /** Set a color tint on the snapshot and supporting views. */
     open fun setColorTint(amount: Float, tintColor: Int) {
-        if (!enableRefactorTaskThumbnail()) {
-            // TODO(b/334832108) Add scrim to new TTV
-            taskThumbnailViewDeprecated.setDimAlpha(amount)
+        taskContainers.forEach {
+            if (!enableRefactorTaskThumbnail()) {
+                // TODO(b/334832108) Add scrim to new TTV
+                it.thumbnailViewDeprecated.dimAlpha = amount
+            }
+            it.iconView.setIconColorTint(tintColor, amount)
+            it.digitalWellBeingToast?.setBannerColorTint(tintColor, amount)
         }
-        iconView.setIconColorTint(tintColor, amount)
-        digitalWellBeingToast.setBannerColorTint(tintColor, amount)
     }
 
     /**
@@ -1327,21 +1337,28 @@
      * @param taskId is only used when setting visibility to a non-[View.VISIBLE] value
      */
     open fun setThumbnailVisibility(visibility: Int, taskId: Int) {
-        children.filterNot { it === iconView }.forEach { it.visibility = visibility }
+        taskContainers.forEach {
+            if (visibility == VISIBLE || it.task.key.id == taskId) {
+                it.snapshotView.visibility = visibility
+                it.digitalWellBeingToast?.setBannerVisibility(visibility)
+                it.showWindowsView?.visibility = visibility
+                it.overlay.setVisibility(visibility)
+            }
+        }
     }
 
     open fun setOverlayEnabled(overlayEnabled: Boolean) {
         // TODO(b/335606129) Investigate the usage of [TaskOverlay] in the new TaskThumbnailView.
         //  and if it's still necessary we should support that in the new TTV class.
         if (!enableRefactorTaskThumbnail()) {
-            taskThumbnailViewDeprecated.setOverlayEnabled(overlayEnabled)
+            taskContainers.forEach { it.thumbnailViewDeprecated.setOverlayEnabled(overlayEnabled) }
         }
     }
 
     protected open fun refreshTaskThumbnailSplash() {
         if (!enableRefactorTaskThumbnail()) {
             // TODO(b/334826842) add splash functionality to new TTV
-            taskThumbnailViewDeprecated.refreshSplashView()
+            taskContainers.forEach { it.thumbnailViewDeprecated.refreshSplashView() }
         }
     }
 
@@ -1365,7 +1382,9 @@
     protected open fun applyThumbnailSplashAlpha() {
         if (!enableRefactorTaskThumbnail()) {
             // TODO(b/334826842) add splash functionality to new TTV
-            taskThumbnailViewDeprecated.setSplashAlpha(taskThumbnailSplashAlpha)
+            taskContainers.forEach {
+                it.thumbnailViewDeprecated.setSplashAlpha(taskThumbnailSplashAlpha)
+            }
         }
     }
 
@@ -1395,18 +1414,23 @@
     }
 
     protected open fun onFullscreenProgressChanged(fullscreenProgress: Float) {
-        iconView.setVisibility(if (fullscreenProgress < 1) VISIBLE else INVISIBLE)
-        taskThumbnailViewDeprecated.taskOverlay.setFullscreenProgress(fullscreenProgress)
+        taskContainers.forEach {
+            it.iconView.setVisibility(if (fullscreenProgress < 1) VISIBLE else INVISIBLE)
+            it.overlay.setFullscreenProgress(fullscreenProgress)
+        }
         setIconsAndBannersFullscreenProgress(fullscreenProgress)
         updateSnapshotRadius()
     }
 
     protected open fun updateSnapshotRadius() {
         updateCurrentFullscreenParams()
-        taskThumbnailViewDeprecated.setFullscreenParams(currentFullscreenParams)
+        taskContainers.forEach {
+            it.thumbnailViewDeprecated.setFullscreenParams(getThumbnailFullscreenParams())
+            it.overlay.setFullscreenParams(getThumbnailFullscreenParams())
+        }
     }
 
-    protected fun updateCurrentFullscreenParams() {
+    protected open fun updateCurrentFullscreenParams() {
         updateFullscreenParams(currentFullscreenParams)
     }
 
@@ -1414,18 +1438,21 @@
         recentsView?.let { fullscreenParams.setProgress(fullscreenProgress, it.scaleX, scaleX) }
     }
 
+    protected open fun getThumbnailFullscreenParams(): FullscreenDrawParams =
+        currentFullscreenParams
+
     private fun onModalnessUpdated(modalness: Float) {
-        iconView.setModalAlpha(1 - modalness)
-        digitalWellBeingToast.updateBannerOffset(modalness)
+        taskContainers.forEach {
+            it.iconView.setModalAlpha(1 - modalness)
+            it.digitalWellBeingToast?.updateBannerOffset(modalness)
+        }
     }
 
     /** Updates [TaskThumbnailView] to reflect the latest [Task] state (i.e., task isRunning). */
     fun notifyIsRunningTaskUpdated() {
         // TODO(b/335649589): TaskView's VM will already have access to TaskThumbnailView's VM
         //  so there will be no need to access TaskThumbnailView's VM through the TaskThumbnailView
-        if (taskContainers.isNotEmpty()) {
-            bindTaskThumbnailView()
-        }
+        taskContainers.forEach { it.bindThumbnailView() }
     }
 
     fun resetPersistentViewTransforms() {
@@ -1458,7 +1485,7 @@
         setColorTint(0f, 0)
         if (!enableRefactorTaskThumbnail()) {
             // TODO(b/335399428) add split select functionality to new TTV
-            taskThumbnailViewDeprecated.resetViewTransforms()
+            taskContainers.forEach { it.thumbnailViewDeprecated.resetViewTransforms() }
         }
     }
 
@@ -1508,21 +1535,61 @@
     /** Holder for all Task dependent information. */
     inner class TaskContainer(
         val task: Task,
-        val thumbnailView: TaskThumbnailViewDeprecated,
+        val thumbnailView: TaskThumbnailView?,
+        val thumbnailViewDeprecated: TaskThumbnailViewDeprecated,
         val iconView: TaskViewIcon,
+        /**
+         * This technically can be a vanilla [android.view.TouchDelegate] class, however that class
+         * requires setting the touch bounds at construction, so we'd repeatedly be created many
+         * instances unnecessarily as scrolling occurs, whereas [TransformingTouchDelegate] allows
+         * touch delegated bounds only to be updated.
+         */
+        val iconTouchDelegate: TransformingTouchDelegate,
         /** Defaults to STAGE_POSITION_UNDEFINED if in not a split screen task view */
-        @field:StagePosition var stagePosition: Int,
-        val digitalWellBeingToast: DigitalWellBeingToast?
+        @StagePosition val stagePosition: Int,
+        val digitalWellBeingToast: DigitalWellBeingToast?,
+        val showWindowsView: View?,
+        taskOverlayFactory: TaskOverlayFactory
     ) {
+        val overlay: TaskOverlay<*> = taskOverlayFactory.createOverlay(this)
+
         @IdRes
         val a11yNodeId: Int =
             if (stagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT) R.id.split_bottomRight_appInfo
             else R.id.split_topLeft_appInfo
 
+        val snapshotView: View
+            get() = thumbnailView ?: thumbnailViewDeprecated
+
+        /** Builds proto for logging */
         val itemInfo: WorkspaceItemInfo
-            get() = getItemInfo(task)
+            get() =
+                WorkspaceItemInfo().apply {
+                    itemType = LauncherSettings.Favorites.ITEM_TYPE_TASK
+                    container = LauncherSettings.Favorites.CONTAINER_TASKSWITCHER
+                    val componentKey = TaskUtils.getLaunchComponentKeyForTask(task.key)
+                    user = componentKey.user
+                    intent = Intent().setComponent(componentKey.componentName)
+                    title = task.title
+                    recentsView?.let { screenId = it.indexOfChild(this@TaskView) }
+                    if (privateSpaceRestrictAccessibilityDrag()) {
+                        if (
+                            UserCache.getInstance(context).getUserInfo(componentKey.user).isPrivate
+                        ) {
+                            runtimeStatusFlags =
+                                runtimeStatusFlags or ItemInfoWithIcon.FLAG_NOT_PINNABLE
+                        }
+                    }
+                }
+
         val taskView: TaskView
             get() = this@TaskView
+
+        // TODO(b/335649589): TaskView's VM will already have access to TaskThumbnailView's VM
+        //  so there will be no need to access TaskThumbnailView's VM through the TaskThumbnailView
+        fun bindThumbnailView() {
+            thumbnailView?.viewModel?.bind(TaskThumbnail(task, isRunningTask))
+        }
     }
 
     companion object {
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimatorTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimatorTest.kt
index c54755b..49e54fb 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimatorTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimatorTest.kt
@@ -299,6 +299,7 @@
         InstrumentationRegistry.getInstrumentation().runOnMainSync {}
         PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Y)
 
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync()
         assertThat(bubbleBarView.isAnimatingNewBubble).isFalse()
         assertThat(bubbleBarView.alpha).isEqualTo(0)
         assertThat(handle.translationY).isEqualTo(0)
diff --git a/quickstep/tests/src/com/android/quickstep/FullscreenDrawParamsTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/FullscreenDrawParamsTest.kt
similarity index 100%
rename from quickstep/tests/src/com/android/quickstep/FullscreenDrawParamsTest.kt
rename to quickstep/tests/multivalentTests/src/com/android/quickstep/FullscreenDrawParamsTest.kt
diff --git a/quickstep/tests/src/com/android/quickstep/HotseatWidthCalculationTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/HotseatWidthCalculationTest.kt
similarity index 100%
rename from quickstep/tests/src/com/android/quickstep/HotseatWidthCalculationTest.kt
rename to quickstep/tests/multivalentTests/src/com/android/quickstep/HotseatWidthCalculationTest.kt
diff --git a/quickstep/tests/src/com/android/quickstep/util/AppPairsControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/AppPairsControllerTest.kt
similarity index 100%
rename from quickstep/tests/src/com/android/quickstep/util/AppPairsControllerTest.kt
rename to quickstep/tests/multivalentTests/src/com/android/quickstep/util/AppPairsControllerTest.kt
diff --git a/quickstep/tests/src/com/android/quickstep/util/RecentsOrientedStateTest.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/RecentsOrientedStateTest.java
similarity index 100%
rename from quickstep/tests/src/com/android/quickstep/util/RecentsOrientedStateTest.java
rename to quickstep/tests/multivalentTests/src/com/android/quickstep/util/RecentsOrientedStateTest.java
diff --git a/quickstep/tests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt
similarity index 98%
rename from quickstep/tests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt
rename to quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt
index f29df61..250dc7b 100644
--- a/quickstep/tests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt
@@ -87,7 +87,7 @@
 
     @Before
     fun setup() {
-        whenever(mockTaskContainer.thumbnailView).thenReturn(mockThumbnailView)
+        whenever(mockTaskContainer.thumbnailViewDeprecated).thenReturn(mockThumbnailView)
         whenever(mockThumbnailView.thumbnail).thenReturn(mockBitmap)
         whenever(mockTaskContainer.iconView).thenReturn(mockIconView)
         whenever(mockIconView.drawable).thenReturn(mockTaskViewDrawable)
@@ -180,7 +180,7 @@
 
         whenever(mockTaskContainer.task).thenReturn(mockTask)
         whenever(mockTaskContainer.iconView).thenReturn(mockIconView)
-        whenever(mockTaskContainer.thumbnailView).thenReturn(mockThumbnailView)
+        whenever(mockTaskContainer.thumbnailViewDeprecated).thenReturn(mockThumbnailView)
         whenever(mockTask.getKey()).thenReturn(mockTaskKey)
         whenever(mockTaskKey.getId()).thenReturn(taskId)
         whenever(mockSplitSelectStateController.initialTaskId).thenReturn(taskId)
diff --git a/quickstep/tests/src/com/android/quickstep/DesktopSystemShortcutTest.kt b/quickstep/tests/src/com/android/quickstep/DesktopSystemShortcutTest.kt
index 33a8e79..36f2ccf 100644
--- a/quickstep/tests/src/com/android/quickstep/DesktopSystemShortcutTest.kt
+++ b/quickstep/tests/src/com/android/quickstep/DesktopSystemShortcutTest.kt
@@ -19,7 +19,7 @@
 import android.content.ComponentName
 import android.content.Intent
 import android.platform.test.flag.junit.SetFlagsRule
-import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn
+import com.android.dx.mockito.inline.extended.ExtendedMockito
 import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
 import com.android.dx.mockito.inline.extended.StaticMockitoSession
 import com.android.launcher3.AbstractFloatingView
@@ -29,6 +29,8 @@
 import com.android.launcher3.model.data.WorkspaceItemInfo
 import com.android.launcher3.uioverrides.QuickstepLauncher
 import com.android.launcher3.util.SplitConfigurationOptions
+import com.android.launcher3.util.TransformingTouchDelegate
+import com.android.quickstep.TaskOverlayFactory.TaskOverlay
 import com.android.quickstep.views.LauncherRecentsView
 import com.android.quickstep.views.TaskThumbnailViewDeprecated
 import com.android.quickstep.views.TaskView
@@ -43,8 +45,10 @@
 import org.junit.Rule
 import org.junit.Test
 import org.mockito.kotlin.any
+import org.mockito.kotlin.doReturn
 import org.mockito.kotlin.eq
 import org.mockito.kotlin.mock
+import org.mockito.kotlin.spy
 import org.mockito.kotlin.verify
 import org.mockito.kotlin.whenever
 import org.mockito.quality.Strictness
@@ -61,10 +65,13 @@
     private val taskView: TaskView = mock()
     private val workspaceItemInfo: WorkspaceItemInfo = mock()
     private val abstractFloatingViewHelper: AbstractFloatingViewHelper = mock()
-    private val thumbnailView: TaskThumbnailViewDeprecated = mock()
+    private val thumbnailViewDeprecated: TaskThumbnailViewDeprecated = mock()
     private val iconView: TaskViewIcon = mock()
+    private val transformingTouchDelegate: TransformingTouchDelegate = mock()
     private val factory: TaskShortcutFactory =
         DesktopSystemShortcut.createFactory(abstractFloatingViewHelper)
+    private val overlayFactory: TaskOverlayFactory = mock()
+    private val overlay: TaskOverlay<*> = mock()
 
     private lateinit var mockitoSession: StaticMockitoSession
 
@@ -75,8 +82,9 @@
                 .strictness(Strictness.LENIENT)
                 .spyStatic(DesktopModeStatus::class.java)
                 .startMocking()
-        doReturn(true).`when` { DesktopModeStatus.enforceDeviceRestrictions() }
-        doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+        ExtendedMockito.doReturn(true).`when` { DesktopModeStatus.enforceDeviceRestrictions() }
+        ExtendedMockito.doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+        whenever(overlayFactory.createOverlay(any())).thenReturn(overlay)
     }
 
     @After
@@ -92,14 +100,7 @@
             Task(TaskKey(1, 0, Intent(), ComponentName("", ""), 0, 2000)).apply {
                 isDockable = true
             }
-        val taskContainer =
-            taskView.TaskContainer(
-                task,
-                thumbnailView,
-                iconView,
-                SplitConfigurationOptions.STAGE_POSITION_UNDEFINED,
-                null
-            )
+        val taskContainer = createTaskContainer(task)
 
         val shortcuts = factory.getShortcuts(launcher, taskContainer)
         assertThat(shortcuts).isNull()
@@ -108,20 +109,9 @@
     @Test
     fun createDesktopTaskShortcutFactory_desktopModeEnabled_DeviceNotSupported() {
         setFlagsRule.enableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
-        doReturn(false).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+        ExtendedMockito.doReturn(false).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
 
-        val task =
-            Task(TaskKey(1, 0, Intent(), ComponentName("", ""), 0, 2000)).apply {
-                isDockable = true
-            }
-        val taskContainer =
-            taskView.TaskContainer(
-                task,
-                thumbnailView,
-                iconView,
-                SplitConfigurationOptions.STAGE_POSITION_UNDEFINED,
-                null
-            )
+        val taskContainer = createTaskContainer(createTask())
 
         val shortcuts = factory.getShortcuts(launcher, taskContainer)
         assertThat(shortcuts).isNull()
@@ -130,21 +120,11 @@
     @Test
     fun createDesktopTaskShortcutFactory_desktopModeEnabled_DeviceNotSupported_OverrideEnabled() {
         setFlagsRule.enableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
-        doReturn(false).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
-        doReturn(false).`when` { DesktopModeStatus.enforceDeviceRestrictions() }
+        ExtendedMockito.doReturn(false).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+        ExtendedMockito.doReturn(false).`when` { DesktopModeStatus.enforceDeviceRestrictions() }
 
-        val task =
-            Task(TaskKey(1, 0, Intent(), ComponentName("", ""), 0, 2000)).apply {
-                isDockable = true
-            }
-        val taskContainer =
-            taskView.TaskContainer(
-                task,
-                thumbnailView,
-                iconView,
-                SplitConfigurationOptions.STAGE_POSITION_UNDEFINED,
-                null
-            )
+        val taskContainer = spy(createTaskContainer(createTask()))
+        doReturn(workspaceItemInfo).whenever(taskContainer).itemInfo
 
         val shortcuts = factory.getShortcuts(launcher, taskContainer)
         assertThat(shortcuts).isNotNull()
@@ -154,18 +134,8 @@
     fun createDesktopTaskShortcutFactory_undockable() {
         setFlagsRule.enableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
 
-        val task =
-            Task(TaskKey(1, 0, Intent(), ComponentName("", ""), 0, 2000)).apply {
-                isDockable = false
-            }
-        val taskContainer =
-            taskView.TaskContainer(
-                task,
-                thumbnailView,
-                iconView,
-                SplitConfigurationOptions.STAGE_POSITION_UNDEFINED,
-                null
-            )
+        val unDockableTask = createTask().apply { isDockable = false }
+        val taskContainer = createTaskContainer(unDockableTask)
 
         val shortcuts = factory.getShortcuts(launcher, taskContainer)
         assertThat(shortcuts).isNull()
@@ -175,28 +145,18 @@
     fun desktopSystemShortcutClicked() {
         setFlagsRule.enableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
 
-        val task =
-            Task(TaskKey(1, 0, Intent(), ComponentName("", ""), 0, 2000)).apply {
-                isDockable = true
-            }
-        val taskContainer =
-            taskView.TaskContainer(
-                task,
-                thumbnailView,
-                iconView,
-                SplitConfigurationOptions.STAGE_POSITION_UNDEFINED,
-                null
-            )
+        val task = createTask()
+        val taskContainer = spy(createTaskContainer(task))
 
         whenever(launcher.getOverviewPanel<LauncherRecentsView>()).thenReturn(recentsView)
         whenever(launcher.statsLogManager).thenReturn(statsLogManager)
         whenever(statsLogManager.logger()).thenReturn(statsLogger)
         whenever(statsLogger.withItemInfo(any())).thenReturn(statsLogger)
-        whenever(taskView.getItemInfo(task)).thenReturn(workspaceItemInfo)
         whenever(recentsView.moveTaskToDesktop(any(), any())).thenAnswer {
             val successCallback = it.getArgument<Runnable>(1)
             successCallback.run()
         }
+        doReturn(workspaceItemInfo).whenever(taskContainer).itemInfo
 
         val shortcuts = factory.getShortcuts(launcher, taskContainer)
         assertThat(shortcuts).hasSize(1)
@@ -213,4 +173,24 @@
         verify(statsLogger).withItemInfo(workspaceItemInfo)
         verify(statsLogger).log(LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_DESKTOP_TAP)
     }
+
+    private fun createTask(): Task {
+        return Task(TaskKey(1, 0, Intent(), ComponentName("", ""), 0, 2000)).apply {
+            isDockable = true
+        }
+    }
+
+    private fun createTaskContainer(task: Task): TaskView.TaskContainer {
+        return taskView.TaskContainer(
+            task,
+            thumbnailView = null,
+            thumbnailViewDeprecated,
+            iconView,
+            transformingTouchDelegate,
+            SplitConfigurationOptions.STAGE_POSITION_UNDEFINED,
+            digitalWellBeingToast = null,
+            showWindowsView = null,
+            overlayFactory
+        )
+    }
 }
diff --git a/quickstep/tests/src/com/android/quickstep/TaplDigitalWellBeingToastTest.java b/quickstep/tests/src/com/android/quickstep/TaplDigitalWellBeingToastTest.java
index 37ab131..07d8f61 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplDigitalWellBeingToastTest.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplDigitalWellBeingToastTest.java
@@ -86,9 +86,10 @@
         final TaskView task = getOnceNotNull("No latest task", launcher -> getLatestTask(launcher));
 
         return getFromLauncher(launcher -> {
+            TaskView.TaskContainer taskContainer = task.getTaskContainers().get(0);
             assertTrue("Latest task is not Calculator", CALCULATOR_PACKAGE.equals(
-                    task.getFirstTask().getTopComponent().getPackageName()));
-            return task.getDigitalWellBeingToast();
+                    taskContainer.getTask().getTopComponent().getPackageName()));
+            return taskContainer.getDigitalWellBeingToast();
         });
     }
 
diff --git a/src/com/android/launcher3/util/rects/Rects.kt b/src/com/android/launcher3/util/rects/Rects.kt
index ac1f3f8..1e6d717 100644
--- a/src/com/android/launcher3/util/rects/Rects.kt
+++ b/src/com/android/launcher3/util/rects/Rects.kt
@@ -21,5 +21,6 @@
 
 /** Copy the coordinates of the [view] relative to its parent into this rectangle. */
 fun Rect.set(view: View) {
-    set(view.left, view.top, view.right, view.bottom)
+    set(0, 0, view.width, view.height)
+    offset(view.x.toInt(), view.y.toInt())
 }
diff --git a/tests/Android.bp b/tests/Android.bp
index 11177de..5794cce 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -192,6 +192,7 @@
     java_resource_dirs: ["config"],
     static_libs: [
         "flag-junit-base",
+        "flag-junit",
         "com_android_launcher3_flags_lib",
         "com_android_wm_shell_flags_lib",
         "androidx.test.uiautomator_uiautomator",
@@ -199,9 +200,10 @@
         "androidx.test.ext.junit",
         "androidx.test.rules",
         "uiautomator-helpers",
-        "mockito-robolectric-prebuilt",
-        "mockito-kotlin2",
+        "inline-mockito-robolectric-prebuilt",
+        "mockito-kotlin-nodeps",
         "platform-parametric-runner-lib",
+        "platform-test-rules-deviceless",
         "testables",
         "Launcher3TestResources",
         "SystemUISharedLib",
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelLandscape3Button_decoupleDepth.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelLandscape3Button_decoupleDepth.txt
new file mode 100644
index 0000000..46cce24
--- /dev/null
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelLandscape3Button_decoupleDepth.txt
@@ -0,0 +1,130 @@
+DeviceProfile:
+	1 dp = 2.625 px
+	isTablet:true
+	isPhone:false
+	transposeLayoutWithOrientation:false
+	isGestureMode:false
+	isLandscape:true
+	isMultiWindowMode:false
+	isTwoPanels:true
+	isLeftRightSplit:true
+	windowX: 0.0px (0.0dp)
+	windowY: 0.0px (0.0dp)
+	widthPx: 2208.0px (841.1429dp)
+	heightPx: 1840.0px (700.9524dp)
+	availableWidthPx: 2208.0px (841.1429dp)
+	availableHeightPx: 1730.0px (659.0476dp)
+	mInsets.left: 0.0px (0.0dp)
+	mInsets.top: 110.0px (41.904762dp)
+	mInsets.right: 0.0px (0.0dp)
+	mInsets.bottom: 0.0px (0.0dp)
+	aspectRatio:1.2
+	isResponsiveGrid:false
+	isScalableGrid:false
+	inv.numRows: 4
+	inv.numColumns: 4
+	inv.numSearchContainerColumns: 4
+	minCellSize: PointF(0.0, 0.0)dp
+	cellWidthPx: 154.0px (58.666668dp)
+	cellHeightPx: 218.0px (83.04762dp)
+	getCellSize().x: 270.0px (102.85714dp)
+	getCellSize().y: 342.0px (130.28572dp)
+	cellLayoutBorderSpacePx Horizontal: 0.0px (0.0dp)
+	cellLayoutBorderSpacePx Vertical: 0.0px (0.0dp)
+	cellLayoutPaddingPx.left: 0.0px (0.0dp)
+	cellLayoutPaddingPx.top: 0.0px (0.0dp)
+	cellLayoutPaddingPx.right: 0.0px (0.0dp)
+	cellLayoutPaddingPx.bottom: 0.0px (0.0dp)
+	iconSizePx: 141.0px (53.714287dp)
+	iconTextSizePx: 34.0px (12.952381dp)
+	iconDrawablePaddingPx: 13.0px (4.952381dp)
+	numFolderRows: 3
+	numFolderColumns: 4
+	folderCellWidthPx: 189.0px (72.0dp)
+	folderCellHeightPx: 219.0px (83.42857dp)
+	folderChildIconSizePx: 141.0px (53.714287dp)
+	folderChildTextSizePx: 34.0px (12.952381dp)
+	folderChildDrawablePaddingPx: 5.0px (1.9047619dp)
+	folderCellLayoutBorderSpacePx.x: 0.0px (0.0dp)
+	folderCellLayoutBorderSpacePx.y: 0.0px (0.0dp)
+	folderContentPaddingLeftRight: 21.0px (8.0dp)
+	folderTopPadding: 63.0px (24.0dp)
+	folderFooterHeight: 147.0px (56.0dp)
+	bottomSheetTopPadding: 110.0px (41.904762dp)
+	bottomSheetOpenDuration: 500
+	bottomSheetCloseDuration: 500
+	bottomSheetWorkspaceScale: 0.97
+	bottomSheetDepth: 0.3
+	allAppsShiftRange: 1840.0px (700.9524dp)
+	allAppsOpenDuration: 500
+	allAppsCloseDuration: 500
+	allAppsIconSizePx: 141.0px (53.714287dp)
+	allAppsIconTextSizePx: 34.0px (12.952381dp)
+	allAppsIconDrawablePaddingPx: 21.0px (8.0dp)
+	allAppsCellHeightPx: 361.0px (137.5238dp)
+	allAppsCellWidthPx: 183.0px (69.71429dp)
+	allAppsBorderSpacePxX: 42.0px (16.0dp)
+	allAppsBorderSpacePxY: 42.0px (16.0dp)
+	numShownAllAppsColumns: 8
+	allAppsPadding.top: 110.0px (41.904762dp)
+	allAppsPadding.left: 42.0px (16.0dp)
+	allAppsPadding.right: 42.0px (16.0dp)
+	allAppsLeftRightMargin: 183.0px (69.71429dp)
+	hotseatBarSizePx: 267.0px (101.71429dp)
+	mHotseatColumnSpan: 4
+	mHotseatWidthPx: 0.0px (0.0dp)
+	hotseatCellHeightPx: 159.0px (60.57143dp)
+	hotseatBarBottomSpacePx: 126.0px (48.0dp)
+	mHotseatBarEdgePaddingPx: 0.0px (0.0dp)
+	mHotseatBarWorkspaceSpacePx: 0.0px (0.0dp)
+	hotseatBarEndOffset: 0.0px (0.0dp)
+	hotseatQsbSpace: 0.0px (0.0dp)
+	hotseatQsbHeight: 0.0px (0.0dp)
+	springLoadedHotseatBarTopMarginPx: 116.0px (44.190475dp)
+	getHotseatLayoutPadding(context).top: 0.0px (0.0dp)
+	getHotseatLayoutPadding(context).bottom: 108.0px (41.142857dp)
+	getHotseatLayoutPadding(context).left: 113.0px (43.04762dp)
+	getHotseatLayoutPadding(context).right: 113.0px (43.04762dp)
+	numShownHotseatIcons: 6
+	hotseatBorderSpace: 0.0px (0.0dp)
+	isQsbInline: false
+	hotseatQsbWidth: 0.0px (0.0dp)
+	isTaskbarPresent:false
+	isTaskbarPresentInApps:true
+	taskbarHeight: 0.0px (0.0dp)
+	stashedTaskbarHeight: 0.0px (0.0dp)
+	taskbarBottomMargin: 0.0px (0.0dp)
+	taskbarIconSize: 0.0px (0.0dp)
+	desiredWorkspaceHorizontalMarginPx: 21.0px (8.0dp)
+	workspacePadding.left: 21.0px (8.0dp)
+	workspacePadding.top: 30.0px (11.428572dp)
+	workspacePadding.right: 21.0px (8.0dp)
+	workspacePadding.bottom: 330.0px (125.71429dp)
+	iconScale: 1.0px (0.3809524dp)
+	cellScaleToFit : 1.0px (0.3809524dp)
+	extraSpace: 498.0px (189.71428dp)
+	unscaled extraSpace: 498.0px (189.71428dp)
+	maxEmptySpace: 0.0px (0.0dp)
+	workspaceTopPadding: 0.0px (0.0dp)
+	workspaceBottomPadding: 0.0px (0.0dp)
+	overviewTaskMarginPx: 0.0px (0.0dp)
+	overviewTaskIconSizePx: 0.0px (0.0dp)
+	overviewTaskIconDrawableSizePx: 0.0px (0.0dp)
+	overviewTaskIconDrawableSizeGridPx: 0.0px (0.0dp)
+	overviewTaskThumbnailTopMarginPx: 0.0px (0.0dp)
+	overviewActionsTopMarginPx: 0.0px (0.0dp)
+	overviewActionsHeight: 0.0px (0.0dp)
+	overviewActionsClaimedSpaceBelow: 0.0px (0.0dp)
+	overviewActionsButtonSpacing: 0.0px (0.0dp)
+	overviewPageSpacing: 0.0px (0.0dp)
+	overviewRowSpacing: 0.0px (0.0dp)
+	overviewGridSideMargin: 0.0px (0.0dp)
+	dropTargetBarTopMarginPx: 0.0px (0.0dp)
+	dropTargetBarSizePx: 147.0px (56.0dp)
+	dropTargetBarBottomMarginPx: 42.0px (16.0dp)
+	getCellLayoutSpringLoadShrunkTop(): 299.0px (113.90476dp)
+	getCellLayoutSpringLoadShrunkBottom(): 1457.0px (555.0476dp)
+	workspaceSpringLoadedMinNextPageVisiblePx: 126.0px (48.0dp)
+	getWorkspaceSpringLoadScale(): 0.8452555px (0.32200208dp)
+	getCellLayoutHeight(): 1370.0px (521.9048dp)
+	getCellLayoutWidth(): 1083.0px (412.57144dp)
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelLandscape_decoupleDepth.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelLandscape_decoupleDepth.txt
new file mode 100644
index 0000000..44b99e9
--- /dev/null
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelLandscape_decoupleDepth.txt
@@ -0,0 +1,130 @@
+DeviceProfile:
+	1 dp = 2.625 px
+	isTablet:true
+	isPhone:false
+	transposeLayoutWithOrientation:false
+	isGestureMode:true
+	isLandscape:true
+	isMultiWindowMode:false
+	isTwoPanels:true
+	isLeftRightSplit:true
+	windowX: 0.0px (0.0dp)
+	windowY: 0.0px (0.0dp)
+	widthPx: 2208.0px (841.1429dp)
+	heightPx: 1840.0px (700.9524dp)
+	availableWidthPx: 2208.0px (841.1429dp)
+	availableHeightPx: 1730.0px (659.0476dp)
+	mInsets.left: 0.0px (0.0dp)
+	mInsets.top: 110.0px (41.904762dp)
+	mInsets.right: 0.0px (0.0dp)
+	mInsets.bottom: 0.0px (0.0dp)
+	aspectRatio:1.2
+	isResponsiveGrid:false
+	isScalableGrid:false
+	inv.numRows: 4
+	inv.numColumns: 4
+	inv.numSearchContainerColumns: 4
+	minCellSize: PointF(0.0, 0.0)dp
+	cellWidthPx: 154.0px (58.666668dp)
+	cellHeightPx: 218.0px (83.04762dp)
+	getCellSize().x: 270.0px (102.85714dp)
+	getCellSize().y: 342.0px (130.28572dp)
+	cellLayoutBorderSpacePx Horizontal: 0.0px (0.0dp)
+	cellLayoutBorderSpacePx Vertical: 0.0px (0.0dp)
+	cellLayoutPaddingPx.left: 0.0px (0.0dp)
+	cellLayoutPaddingPx.top: 0.0px (0.0dp)
+	cellLayoutPaddingPx.right: 0.0px (0.0dp)
+	cellLayoutPaddingPx.bottom: 0.0px (0.0dp)
+	iconSizePx: 141.0px (53.714287dp)
+	iconTextSizePx: 34.0px (12.952381dp)
+	iconDrawablePaddingPx: 13.0px (4.952381dp)
+	numFolderRows: 3
+	numFolderColumns: 4
+	folderCellWidthPx: 189.0px (72.0dp)
+	folderCellHeightPx: 219.0px (83.42857dp)
+	folderChildIconSizePx: 141.0px (53.714287dp)
+	folderChildTextSizePx: 34.0px (12.952381dp)
+	folderChildDrawablePaddingPx: 5.0px (1.9047619dp)
+	folderCellLayoutBorderSpacePx.x: 0.0px (0.0dp)
+	folderCellLayoutBorderSpacePx.y: 0.0px (0.0dp)
+	folderContentPaddingLeftRight: 21.0px (8.0dp)
+	folderTopPadding: 63.0px (24.0dp)
+	folderFooterHeight: 147.0px (56.0dp)
+	bottomSheetTopPadding: 110.0px (41.904762dp)
+	bottomSheetOpenDuration: 500
+	bottomSheetCloseDuration: 500
+	bottomSheetWorkspaceScale: 0.97
+	bottomSheetDepth: 0.3
+	allAppsShiftRange: 1840.0px (700.9524dp)
+	allAppsOpenDuration: 500
+	allAppsCloseDuration: 500
+	allAppsIconSizePx: 141.0px (53.714287dp)
+	allAppsIconTextSizePx: 34.0px (12.952381dp)
+	allAppsIconDrawablePaddingPx: 21.0px (8.0dp)
+	allAppsCellHeightPx: 361.0px (137.5238dp)
+	allAppsCellWidthPx: 183.0px (69.71429dp)
+	allAppsBorderSpacePxX: 42.0px (16.0dp)
+	allAppsBorderSpacePxY: 42.0px (16.0dp)
+	numShownAllAppsColumns: 8
+	allAppsPadding.top: 110.0px (41.904762dp)
+	allAppsPadding.left: 42.0px (16.0dp)
+	allAppsPadding.right: 42.0px (16.0dp)
+	allAppsLeftRightMargin: 183.0px (69.71429dp)
+	hotseatBarSizePx: 267.0px (101.71429dp)
+	mHotseatColumnSpan: 4
+	mHotseatWidthPx: 0.0px (0.0dp)
+	hotseatCellHeightPx: 159.0px (60.57143dp)
+	hotseatBarBottomSpacePx: 126.0px (48.0dp)
+	mHotseatBarEdgePaddingPx: 0.0px (0.0dp)
+	mHotseatBarWorkspaceSpacePx: 0.0px (0.0dp)
+	hotseatBarEndOffset: 0.0px (0.0dp)
+	hotseatQsbSpace: 0.0px (0.0dp)
+	hotseatQsbHeight: 0.0px (0.0dp)
+	springLoadedHotseatBarTopMarginPx: 116.0px (44.190475dp)
+	getHotseatLayoutPadding(context).top: 0.0px (0.0dp)
+	getHotseatLayoutPadding(context).bottom: 108.0px (41.142857dp)
+	getHotseatLayoutPadding(context).left: 113.0px (43.04762dp)
+	getHotseatLayoutPadding(context).right: 113.0px (43.04762dp)
+	numShownHotseatIcons: 6
+	hotseatBorderSpace: 0.0px (0.0dp)
+	isQsbInline: false
+	hotseatQsbWidth: 0.0px (0.0dp)
+	isTaskbarPresent:false
+	isTaskbarPresentInApps:true
+	taskbarHeight: 0.0px (0.0dp)
+	stashedTaskbarHeight: 0.0px (0.0dp)
+	taskbarBottomMargin: 0.0px (0.0dp)
+	taskbarIconSize: 0.0px (0.0dp)
+	desiredWorkspaceHorizontalMarginPx: 21.0px (8.0dp)
+	workspacePadding.left: 21.0px (8.0dp)
+	workspacePadding.top: 30.0px (11.428572dp)
+	workspacePadding.right: 21.0px (8.0dp)
+	workspacePadding.bottom: 330.0px (125.71429dp)
+	iconScale: 1.0px (0.3809524dp)
+	cellScaleToFit : 1.0px (0.3809524dp)
+	extraSpace: 498.0px (189.71428dp)
+	unscaled extraSpace: 498.0px (189.71428dp)
+	maxEmptySpace: 0.0px (0.0dp)
+	workspaceTopPadding: 0.0px (0.0dp)
+	workspaceBottomPadding: 0.0px (0.0dp)
+	overviewTaskMarginPx: 0.0px (0.0dp)
+	overviewTaskIconSizePx: 0.0px (0.0dp)
+	overviewTaskIconDrawableSizePx: 0.0px (0.0dp)
+	overviewTaskIconDrawableSizeGridPx: 0.0px (0.0dp)
+	overviewTaskThumbnailTopMarginPx: 0.0px (0.0dp)
+	overviewActionsTopMarginPx: 0.0px (0.0dp)
+	overviewActionsHeight: 0.0px (0.0dp)
+	overviewActionsClaimedSpaceBelow: 0.0px (0.0dp)
+	overviewActionsButtonSpacing: 0.0px (0.0dp)
+	overviewPageSpacing: 0.0px (0.0dp)
+	overviewRowSpacing: 0.0px (0.0dp)
+	overviewGridSideMargin: 0.0px (0.0dp)
+	dropTargetBarTopMarginPx: 0.0px (0.0dp)
+	dropTargetBarSizePx: 147.0px (56.0dp)
+	dropTargetBarBottomMarginPx: 42.0px (16.0dp)
+	getCellLayoutSpringLoadShrunkTop(): 299.0px (113.90476dp)
+	getCellLayoutSpringLoadShrunkBottom(): 1457.0px (555.0476dp)
+	workspaceSpringLoadedMinNextPageVisiblePx: 126.0px (48.0dp)
+	getWorkspaceSpringLoadScale(): 0.8452555px (0.32200208dp)
+	getCellLayoutHeight(): 1370.0px (521.9048dp)
+	getCellLayoutWidth(): 1083.0px (412.57144dp)
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelPortrait3Button_decoupleDepth.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelPortrait3Button_decoupleDepth.txt
new file mode 100644
index 0000000..e7b72f2
--- /dev/null
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelPortrait3Button_decoupleDepth.txt
@@ -0,0 +1,130 @@
+DeviceProfile:
+	1 dp = 2.625 px
+	isTablet:true
+	isPhone:false
+	transposeLayoutWithOrientation:false
+	isGestureMode:false
+	isLandscape:false
+	isMultiWindowMode:false
+	isTwoPanels:true
+	isLeftRightSplit:false
+	windowX: 0.0px (0.0dp)
+	windowY: 0.0px (0.0dp)
+	widthPx: 1840.0px (700.9524dp)
+	heightPx: 2208.0px (841.1429dp)
+	availableWidthPx: 1840.0px (700.9524dp)
+	availableHeightPx: 2075.0px (790.4762dp)
+	mInsets.left: 0.0px (0.0dp)
+	mInsets.top: 133.0px (50.666668dp)
+	mInsets.right: 0.0px (0.0dp)
+	mInsets.bottom: 0.0px (0.0dp)
+	aspectRatio:1.2
+	isResponsiveGrid:false
+	isScalableGrid:false
+	inv.numRows: 4
+	inv.numColumns: 4
+	inv.numSearchContainerColumns: 4
+	minCellSize: PointF(0.0, 0.0)dp
+	cellWidthPx: 154.0px (58.666668dp)
+	cellHeightPx: 218.0px (83.04762dp)
+	getCellSize().x: 224.0px (85.333336dp)
+	getCellSize().y: 430.0px (163.80952dp)
+	cellLayoutBorderSpacePx Horizontal: 0.0px (0.0dp)
+	cellLayoutBorderSpacePx Vertical: 0.0px (0.0dp)
+	cellLayoutPaddingPx.left: 0.0px (0.0dp)
+	cellLayoutPaddingPx.top: 0.0px (0.0dp)
+	cellLayoutPaddingPx.right: 0.0px (0.0dp)
+	cellLayoutPaddingPx.bottom: 0.0px (0.0dp)
+	iconSizePx: 141.0px (53.714287dp)
+	iconTextSizePx: 34.0px (12.952381dp)
+	iconDrawablePaddingPx: 13.0px (4.952381dp)
+	numFolderRows: 3
+	numFolderColumns: 4
+	folderCellWidthPx: 189.0px (72.0dp)
+	folderCellHeightPx: 219.0px (83.42857dp)
+	folderChildIconSizePx: 141.0px (53.714287dp)
+	folderChildTextSizePx: 34.0px (12.952381dp)
+	folderChildDrawablePaddingPx: 5.0px (1.9047619dp)
+	folderCellLayoutBorderSpacePx.x: 0.0px (0.0dp)
+	folderCellLayoutBorderSpacePx.y: 0.0px (0.0dp)
+	folderContentPaddingLeftRight: 21.0px (8.0dp)
+	folderTopPadding: 63.0px (24.0dp)
+	folderFooterHeight: 147.0px (56.0dp)
+	bottomSheetTopPadding: 133.0px (50.666668dp)
+	bottomSheetOpenDuration: 500
+	bottomSheetCloseDuration: 500
+	bottomSheetWorkspaceScale: 0.97
+	bottomSheetDepth: 0.3
+	allAppsShiftRange: 2208.0px (841.1429dp)
+	allAppsOpenDuration: 500
+	allAppsCloseDuration: 500
+	allAppsIconSizePx: 141.0px (53.714287dp)
+	allAppsIconTextSizePx: 34.0px (12.952381dp)
+	allAppsIconDrawablePaddingPx: 21.0px (8.0dp)
+	allAppsCellHeightPx: 361.0px (137.5238dp)
+	allAppsCellWidthPx: 183.0px (69.71429dp)
+	allAppsBorderSpacePxX: 42.0px (16.0dp)
+	allAppsBorderSpacePxY: 42.0px (16.0dp)
+	numShownAllAppsColumns: 8
+	allAppsPadding.top: 133.0px (50.666668dp)
+	allAppsPadding.left: 42.0px (16.0dp)
+	allAppsPadding.right: 42.0px (16.0dp)
+	allAppsLeftRightMargin: 1.0px (0.3809524dp)
+	hotseatBarSizePx: 267.0px (101.71429dp)
+	mHotseatColumnSpan: 4
+	mHotseatWidthPx: 0.0px (0.0dp)
+	hotseatCellHeightPx: 159.0px (60.57143dp)
+	hotseatBarBottomSpacePx: 126.0px (48.0dp)
+	mHotseatBarEdgePaddingPx: 0.0px (0.0dp)
+	mHotseatBarWorkspaceSpacePx: 0.0px (0.0dp)
+	hotseatBarEndOffset: 0.0px (0.0dp)
+	hotseatQsbSpace: 0.0px (0.0dp)
+	hotseatQsbHeight: 0.0px (0.0dp)
+	springLoadedHotseatBarTopMarginPx: 168.0px (64.0dp)
+	getHotseatLayoutPadding(context).top: 0.0px (0.0dp)
+	getHotseatLayoutPadding(context).bottom: 108.0px (41.142857dp)
+	getHotseatLayoutPadding(context).left: 98.0px (37.333332dp)
+	getHotseatLayoutPadding(context).right: 98.0px (37.333332dp)
+	numShownHotseatIcons: 6
+	hotseatBorderSpace: 0.0px (0.0dp)
+	isQsbInline: false
+	hotseatQsbWidth: 0.0px (0.0dp)
+	isTaskbarPresent:false
+	isTaskbarPresentInApps:true
+	taskbarHeight: 0.0px (0.0dp)
+	stashedTaskbarHeight: 0.0px (0.0dp)
+	taskbarBottomMargin: 0.0px (0.0dp)
+	taskbarIconSize: 0.0px (0.0dp)
+	desiredWorkspaceHorizontalMarginPx: 21.0px (8.0dp)
+	workspacePadding.left: 21.0px (8.0dp)
+	workspacePadding.top: 24.0px (9.142858dp)
+	workspacePadding.right: 21.0px (8.0dp)
+	workspacePadding.bottom: 330.0px (125.71429dp)
+	iconScale: 1.0px (0.3809524dp)
+	cellScaleToFit : 1.0px (0.3809524dp)
+	extraSpace: 849.0px (323.42856dp)
+	unscaled extraSpace: 849.0px (323.42856dp)
+	maxEmptySpace: 0.0px (0.0dp)
+	workspaceTopPadding: 0.0px (0.0dp)
+	workspaceBottomPadding: 0.0px (0.0dp)
+	overviewTaskMarginPx: 0.0px (0.0dp)
+	overviewTaskIconSizePx: 0.0px (0.0dp)
+	overviewTaskIconDrawableSizePx: 0.0px (0.0dp)
+	overviewTaskIconDrawableSizeGridPx: 0.0px (0.0dp)
+	overviewTaskThumbnailTopMarginPx: 0.0px (0.0dp)
+	overviewActionsTopMarginPx: 0.0px (0.0dp)
+	overviewActionsHeight: 0.0px (0.0dp)
+	overviewActionsClaimedSpaceBelow: 0.0px (0.0dp)
+	overviewActionsButtonSpacing: 0.0px (0.0dp)
+	overviewPageSpacing: 0.0px (0.0dp)
+	overviewRowSpacing: 0.0px (0.0dp)
+	overviewGridSideMargin: 0.0px (0.0dp)
+	dropTargetBarTopMarginPx: 0.0px (0.0dp)
+	dropTargetBarSizePx: 147.0px (56.0dp)
+	dropTargetBarBottomMarginPx: 84.0px (32.0dp)
+	getCellLayoutSpringLoadShrunkTop(): 364.0px (138.66667dp)
+	getCellLayoutSpringLoadShrunkBottom(): 1773.0px (675.4286dp)
+	workspaceSpringLoadedMinNextPageVisiblePx: 126.0px (48.0dp)
+	getWorkspaceSpringLoadScale(): 0.81871px (0.31188953dp)
+	getCellLayoutHeight(): 1721.0px (655.619dp)
+	getCellLayoutWidth(): 899.0px (342.4762dp)
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelPortrait_decoupleDepth.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelPortrait_decoupleDepth.txt
new file mode 100644
index 0000000..eae50f1
--- /dev/null
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelPortrait_decoupleDepth.txt
@@ -0,0 +1,130 @@
+DeviceProfile:
+	1 dp = 2.625 px
+	isTablet:true
+	isPhone:false
+	transposeLayoutWithOrientation:false
+	isGestureMode:true
+	isLandscape:false
+	isMultiWindowMode:false
+	isTwoPanels:true
+	isLeftRightSplit:false
+	windowX: 0.0px (0.0dp)
+	windowY: 0.0px (0.0dp)
+	widthPx: 1840.0px (700.9524dp)
+	heightPx: 2208.0px (841.1429dp)
+	availableWidthPx: 1840.0px (700.9524dp)
+	availableHeightPx: 2075.0px (790.4762dp)
+	mInsets.left: 0.0px (0.0dp)
+	mInsets.top: 133.0px (50.666668dp)
+	mInsets.right: 0.0px (0.0dp)
+	mInsets.bottom: 0.0px (0.0dp)
+	aspectRatio:1.2
+	isResponsiveGrid:false
+	isScalableGrid:false
+	inv.numRows: 4
+	inv.numColumns: 4
+	inv.numSearchContainerColumns: 4
+	minCellSize: PointF(0.0, 0.0)dp
+	cellWidthPx: 154.0px (58.666668dp)
+	cellHeightPx: 218.0px (83.04762dp)
+	getCellSize().x: 224.0px (85.333336dp)
+	getCellSize().y: 430.0px (163.80952dp)
+	cellLayoutBorderSpacePx Horizontal: 0.0px (0.0dp)
+	cellLayoutBorderSpacePx Vertical: 0.0px (0.0dp)
+	cellLayoutPaddingPx.left: 0.0px (0.0dp)
+	cellLayoutPaddingPx.top: 0.0px (0.0dp)
+	cellLayoutPaddingPx.right: 0.0px (0.0dp)
+	cellLayoutPaddingPx.bottom: 0.0px (0.0dp)
+	iconSizePx: 141.0px (53.714287dp)
+	iconTextSizePx: 34.0px (12.952381dp)
+	iconDrawablePaddingPx: 13.0px (4.952381dp)
+	numFolderRows: 3
+	numFolderColumns: 4
+	folderCellWidthPx: 189.0px (72.0dp)
+	folderCellHeightPx: 219.0px (83.42857dp)
+	folderChildIconSizePx: 141.0px (53.714287dp)
+	folderChildTextSizePx: 34.0px (12.952381dp)
+	folderChildDrawablePaddingPx: 5.0px (1.9047619dp)
+	folderCellLayoutBorderSpacePx.x: 0.0px (0.0dp)
+	folderCellLayoutBorderSpacePx.y: 0.0px (0.0dp)
+	folderContentPaddingLeftRight: 21.0px (8.0dp)
+	folderTopPadding: 63.0px (24.0dp)
+	folderFooterHeight: 147.0px (56.0dp)
+	bottomSheetTopPadding: 133.0px (50.666668dp)
+	bottomSheetOpenDuration: 500
+	bottomSheetCloseDuration: 500
+	bottomSheetWorkspaceScale: 0.97
+	bottomSheetDepth: 0.3
+	allAppsShiftRange: 2208.0px (841.1429dp)
+	allAppsOpenDuration: 500
+	allAppsCloseDuration: 500
+	allAppsIconSizePx: 141.0px (53.714287dp)
+	allAppsIconTextSizePx: 34.0px (12.952381dp)
+	allAppsIconDrawablePaddingPx: 21.0px (8.0dp)
+	allAppsCellHeightPx: 361.0px (137.5238dp)
+	allAppsCellWidthPx: 183.0px (69.71429dp)
+	allAppsBorderSpacePxX: 42.0px (16.0dp)
+	allAppsBorderSpacePxY: 42.0px (16.0dp)
+	numShownAllAppsColumns: 8
+	allAppsPadding.top: 133.0px (50.666668dp)
+	allAppsPadding.left: 42.0px (16.0dp)
+	allAppsPadding.right: 42.0px (16.0dp)
+	allAppsLeftRightMargin: 1.0px (0.3809524dp)
+	hotseatBarSizePx: 267.0px (101.71429dp)
+	mHotseatColumnSpan: 4
+	mHotseatWidthPx: 0.0px (0.0dp)
+	hotseatCellHeightPx: 159.0px (60.57143dp)
+	hotseatBarBottomSpacePx: 126.0px (48.0dp)
+	mHotseatBarEdgePaddingPx: 0.0px (0.0dp)
+	mHotseatBarWorkspaceSpacePx: 0.0px (0.0dp)
+	hotseatBarEndOffset: 0.0px (0.0dp)
+	hotseatQsbSpace: 0.0px (0.0dp)
+	hotseatQsbHeight: 0.0px (0.0dp)
+	springLoadedHotseatBarTopMarginPx: 168.0px (64.0dp)
+	getHotseatLayoutPadding(context).top: 0.0px (0.0dp)
+	getHotseatLayoutPadding(context).bottom: 108.0px (41.142857dp)
+	getHotseatLayoutPadding(context).left: 98.0px (37.333332dp)
+	getHotseatLayoutPadding(context).right: 98.0px (37.333332dp)
+	numShownHotseatIcons: 6
+	hotseatBorderSpace: 0.0px (0.0dp)
+	isQsbInline: false
+	hotseatQsbWidth: 0.0px (0.0dp)
+	isTaskbarPresent:false
+	isTaskbarPresentInApps:true
+	taskbarHeight: 0.0px (0.0dp)
+	stashedTaskbarHeight: 0.0px (0.0dp)
+	taskbarBottomMargin: 0.0px (0.0dp)
+	taskbarIconSize: 0.0px (0.0dp)
+	desiredWorkspaceHorizontalMarginPx: 21.0px (8.0dp)
+	workspacePadding.left: 21.0px (8.0dp)
+	workspacePadding.top: 24.0px (9.142858dp)
+	workspacePadding.right: 21.0px (8.0dp)
+	workspacePadding.bottom: 330.0px (125.71429dp)
+	iconScale: 1.0px (0.3809524dp)
+	cellScaleToFit : 1.0px (0.3809524dp)
+	extraSpace: 849.0px (323.42856dp)
+	unscaled extraSpace: 849.0px (323.42856dp)
+	maxEmptySpace: 0.0px (0.0dp)
+	workspaceTopPadding: 0.0px (0.0dp)
+	workspaceBottomPadding: 0.0px (0.0dp)
+	overviewTaskMarginPx: 0.0px (0.0dp)
+	overviewTaskIconSizePx: 0.0px (0.0dp)
+	overviewTaskIconDrawableSizePx: 0.0px (0.0dp)
+	overviewTaskIconDrawableSizeGridPx: 0.0px (0.0dp)
+	overviewTaskThumbnailTopMarginPx: 0.0px (0.0dp)
+	overviewActionsTopMarginPx: 0.0px (0.0dp)
+	overviewActionsHeight: 0.0px (0.0dp)
+	overviewActionsClaimedSpaceBelow: 0.0px (0.0dp)
+	overviewActionsButtonSpacing: 0.0px (0.0dp)
+	overviewPageSpacing: 0.0px (0.0dp)
+	overviewRowSpacing: 0.0px (0.0dp)
+	overviewGridSideMargin: 0.0px (0.0dp)
+	dropTargetBarTopMarginPx: 0.0px (0.0dp)
+	dropTargetBarSizePx: 147.0px (56.0dp)
+	dropTargetBarBottomMarginPx: 84.0px (32.0dp)
+	getCellLayoutSpringLoadShrunkTop(): 364.0px (138.66667dp)
+	getCellLayoutSpringLoadShrunkBottom(): 1773.0px (675.4286dp)
+	workspaceSpringLoadedMinNextPageVisiblePx: 126.0px (48.0dp)
+	getWorkspaceSpringLoadScale(): 0.81871px (0.31188953dp)
+	getCellLayoutHeight(): 1721.0px (655.619dp)
+	getCellLayoutWidth(): 899.0px (342.4762dp)
diff --git a/tests/src/com/android/launcher3/AbstractDeviceProfileTest.kt b/tests/multivalentTests/src/com/android/launcher3/AbstractDeviceProfileTest.kt
similarity index 100%
rename from tests/src/com/android/launcher3/AbstractDeviceProfileTest.kt
rename to tests/multivalentTests/src/com/android/launcher3/AbstractDeviceProfileTest.kt
diff --git a/tests/src/com/android/launcher3/DeleteDropTargetTest.kt b/tests/multivalentTests/src/com/android/launcher3/DeleteDropTargetTest.kt
similarity index 100%
rename from tests/src/com/android/launcher3/DeleteDropTargetTest.kt
rename to tests/multivalentTests/src/com/android/launcher3/DeleteDropTargetTest.kt
diff --git a/tests/src/com/android/launcher3/FakeInvariantDeviceProfileTest.kt b/tests/multivalentTests/src/com/android/launcher3/FakeInvariantDeviceProfileTest.kt
similarity index 100%
rename from tests/src/com/android/launcher3/FakeInvariantDeviceProfileTest.kt
rename to tests/multivalentTests/src/com/android/launcher3/FakeInvariantDeviceProfileTest.kt
diff --git a/tests/src/com/android/launcher3/LauncherPrefsTest.kt b/tests/multivalentTests/src/com/android/launcher3/LauncherPrefsTest.kt
similarity index 100%
rename from tests/src/com/android/launcher3/LauncherPrefsTest.kt
rename to tests/multivalentTests/src/com/android/launcher3/LauncherPrefsTest.kt
diff --git a/tests/src/com/android/launcher3/allapps/AlphabeticalAppsListTest.java b/tests/multivalentTests/src/com/android/launcher3/allapps/AlphabeticalAppsListTest.java
similarity index 100%
rename from tests/src/com/android/launcher3/allapps/AlphabeticalAppsListTest.java
rename to tests/multivalentTests/src/com/android/launcher3/allapps/AlphabeticalAppsListTest.java
diff --git a/tests/src/com/android/launcher3/folder/FolderNameProviderTest.java b/tests/multivalentTests/src/com/android/launcher3/folder/FolderNameProviderTest.java
similarity index 100%
rename from tests/src/com/android/launcher3/folder/FolderNameProviderTest.java
rename to tests/multivalentTests/src/com/android/launcher3/folder/FolderNameProviderTest.java
diff --git a/tests/src/com/android/launcher3/icons/IconCacheTest.java b/tests/multivalentTests/src/com/android/launcher3/icons/IconCacheTest.java
similarity index 100%
rename from tests/src/com/android/launcher3/icons/IconCacheTest.java
rename to tests/multivalentTests/src/com/android/launcher3/icons/IconCacheTest.java
diff --git a/tests/src/com/android/launcher3/model/DatabaseHelperTest.kt b/tests/multivalentTests/src/com/android/launcher3/model/DatabaseHelperTest.kt
similarity index 100%
rename from tests/src/com/android/launcher3/model/DatabaseHelperTest.kt
rename to tests/multivalentTests/src/com/android/launcher3/model/DatabaseHelperTest.kt
diff --git a/tests/src/com/android/launcher3/model/GridSizeMigrationUtilTest.kt b/tests/multivalentTests/src/com/android/launcher3/model/GridSizeMigrationUtilTest.kt
similarity index 100%
rename from tests/src/com/android/launcher3/model/GridSizeMigrationUtilTest.kt
rename to tests/multivalentTests/src/com/android/launcher3/model/GridSizeMigrationUtilTest.kt
diff --git a/tests/src/com/android/launcher3/responsive/SizeSpecTest.kt b/tests/multivalentTests/src/com/android/launcher3/responsive/SizeSpecTest.kt
similarity index 100%
rename from tests/src/com/android/launcher3/responsive/SizeSpecTest.kt
rename to tests/multivalentTests/src/com/android/launcher3/responsive/SizeSpecTest.kt
diff --git a/tests/src/com/android/launcher3/ui/ActivityAllAppsContainerViewTest.java b/tests/multivalentTests/src/com/android/launcher3/ui/ActivityAllAppsContainerViewTest.java
similarity index 100%
rename from tests/src/com/android/launcher3/ui/ActivityAllAppsContainerViewTest.java
rename to tests/multivalentTests/src/com/android/launcher3/ui/ActivityAllAppsContainerViewTest.java
diff --git a/tests/src/com/android/launcher3/util/CellContentDimensionsTest.kt b/tests/multivalentTests/src/com/android/launcher3/util/CellContentDimensionsTest.kt
similarity index 100%
rename from tests/src/com/android/launcher3/util/CellContentDimensionsTest.kt
rename to tests/multivalentTests/src/com/android/launcher3/util/CellContentDimensionsTest.kt
diff --git a/tests/src/com/android/launcher3/util/PackageManagerHelperTest.java b/tests/multivalentTests/src/com/android/launcher3/util/PackageManagerHelperTest.java
similarity index 100%
rename from tests/src/com/android/launcher3/util/PackageManagerHelperTest.java
rename to tests/multivalentTests/src/com/android/launcher3/util/PackageManagerHelperTest.java
diff --git a/tests/src/com/android/launcher3/util/rule/SetFlagsRuleExt.kt b/tests/multivalentTests/src/com/android/launcher3/util/rule/SetFlagsRuleExt.kt
similarity index 100%
rename from tests/src/com/android/launcher3/util/rule/SetFlagsRuleExt.kt
rename to tests/multivalentTests/src/com/android/launcher3/util/rule/SetFlagsRuleExt.kt
diff --git a/tests/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfoTest.java b/tests/multivalentTests/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfoTest.java
similarity index 100%
rename from tests/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfoTest.java
rename to tests/multivalentTests/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfoTest.java
diff --git a/tests/src/com/android/launcher3/AppFilterTest.kt b/tests/src/com/android/launcher3/AppFilterTest.kt
new file mode 100644
index 0000000..f2150a2
--- /dev/null
+++ b/tests/src/com/android/launcher3/AppFilterTest.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.res.Resources
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.`when`
+import org.mockito.junit.MockitoJUnitRunner
+
+@RunWith(MockitoJUnitRunner::class)
+class AppFilterTest {
+
+    @Mock private lateinit var mockContext: Context
+
+    @Mock // Mock the Resources object as well
+    private lateinit var mockResources: Resources
+
+    private lateinit var appFilter: AppFilter
+
+    @Before
+    fun setUp() {
+        `when`(mockContext.resources).thenReturn(mockResources) // Link the context and resources
+        `when`(mockResources.getStringArray(R.array.filtered_components))
+            .thenReturn(arrayOf("com.example.app1/Activity1"))
+        appFilter = AppFilter(mockContext)
+    }
+
+    @Test
+    fun shouldShowApp_notFiltered_returnsTrue() {
+        val appToShow = ComponentName("com.example.app2", "Activity2")
+        assertThat(appFilter.shouldShowApp(appToShow)).isTrue()
+    }
+
+    @Test
+    fun shouldShowApp_filtered_returnsFalse() {
+        val appToHide = ComponentName("com.example.app1", "Activity1")
+        assertThat(appFilter.shouldShowApp(appToHide)).isFalse()
+    }
+}
diff --git a/tests/src/com/android/launcher3/nonquickstep/DeviceProfileDumpTest.kt b/tests/src/com/android/launcher3/nonquickstep/DeviceProfileDumpTest.kt
index 9409ac1..60385a7 100644
--- a/tests/src/com/android/launcher3/nonquickstep/DeviceProfileDumpTest.kt
+++ b/tests/src/com/android/launcher3/nonquickstep/DeviceProfileDumpTest.kt
@@ -15,137 +15,105 @@
  */
 package com.android.launcher3.nonquickstep
 
-import android.content.Context
-import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
-import androidx.test.platform.app.InstrumentationRegistry
 import com.android.launcher3.AbstractDeviceProfileTest
 import com.android.launcher3.DeviceProfile
+import com.android.launcher3.Flags
 import com.android.launcher3.InvariantDeviceProfile
-import com.google.common.truth.Truth.assertThat
+import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
 
 /** Tests for DeviceProfile. */
 @SmallTest
-@RunWith(AndroidJUnit4::class)
+@RunWith(Parameterized::class)
 class DeviceProfileDumpTest : AbstractDeviceProfileTest() {
     private val folderName: String = "DeviceProfileDumpTest"
-    @Test
-    fun phonePortrait3Button() {
-        initializeVarsForPhone(deviceSpecs["phone"]!!, isGestureMode = false)
-        val dp = getDeviceProfileForGrid("5_by_5")
 
-        assertDump(dp, "phonePortrait3Button")
+    @Parameterized.Parameter lateinit var instance: TestCase
+
+    @Before
+    fun setUp() {
+        if (instance.decoupleDepth) {
+            setFlagsRule.enableFlags(Flags.FLAG_ENABLE_SCALING_REVEAL_HOME_ANIMATION)
+        } else {
+            setFlagsRule.disableFlags(Flags.FLAG_ENABLE_SCALING_REVEAL_HOME_ANIMATION)
+        }
     }
 
     @Test
-    fun phonePortrait() {
-        initializeVarsForPhone(deviceSpecs["phone"]!!)
-        val dp = getDeviceProfileForGrid("5_by_5")
+    fun dumpPortraitGesture() {
+        initializeDevice(instance.deviceName, isGestureMode = true, isLandscape = false)
+        val dp = getDeviceProfileForGrid(instance.gridName)
+        dp.isTaskbarPresentInApps = instance.isTaskbarPresentInApps
 
-        assertDump(dp, "phonePortrait")
+        assertDump(dp, instance.filename("Portrait"))
     }
 
     @Test
-    fun phoneVerticalBar3Button() {
-        initializeVarsForPhone(deviceSpecs["phone"]!!, isVerticalBar = true, isGestureMode = false)
-        val dp = getDeviceProfileForGrid("5_by_5")
+    fun dumpPortrait3Button() {
+        initializeDevice(instance.deviceName, isGestureMode = false, isLandscape = false)
+        val dp = getDeviceProfileForGrid(instance.gridName)
+        dp.isTaskbarPresentInApps = instance.isTaskbarPresentInApps
 
-        assertDump(dp, "phoneVerticalBar3Button")
+        assertDump(dp, instance.filename("Portrait3Button"))
     }
 
     @Test
-    fun phoneVerticalBar() {
-        initializeVarsForPhone(deviceSpecs["phone"]!!, isVerticalBar = true)
-        val dp = getDeviceProfileForGrid("5_by_5")
+    fun dumpLandscapeGesture() {
+        initializeDevice(instance.deviceName, isGestureMode = true, isLandscape = true)
+        val dp = getDeviceProfileForGrid(instance.gridName)
+        dp.isTaskbarPresentInApps = instance.isTaskbarPresentInApps
 
-        assertDump(dp, "phoneVerticalBar")
+        val testName =
+            if (instance.deviceName == "phone") {
+                "VerticalBar"
+            } else {
+                "Landscape"
+            }
+        assertDump(dp, instance.filename(testName))
     }
 
     @Test
-    fun tabletLandscape3Button() {
-        initializeVarsForTablet(deviceSpecs["tablet"]!!, isLandscape = true, isGestureMode = false)
-        val dp = getDeviceProfileForGrid("6_by_5")
-        dp.isTaskbarPresentInApps = true
+    fun dumpLandscape3Button() {
+        initializeDevice(instance.deviceName, isGestureMode = false, isLandscape = true)
+        val dp = getDeviceProfileForGrid(instance.gridName)
+        dp.isTaskbarPresentInApps = instance.isTaskbarPresentInApps
 
-        assertDump(dp, "tabletLandscape3Button")
+        val testName =
+            if (instance.deviceName == "phone") {
+                "VerticalBar3Button"
+            } else {
+                "Landscape3Button"
+            }
+        assertDump(dp, instance.filename(testName))
     }
 
-    @Test
-    fun tabletLandscape() {
-        initializeVarsForTablet(deviceSpecs["tablet"]!!, isLandscape = true)
-        val dp = getDeviceProfileForGrid("6_by_5")
-        dp.isTaskbarPresentInApps = true
-
-        assertDump(dp, "tabletLandscape")
-    }
-
-    @Test
-    fun tabletPortrait3Button() {
-        initializeVarsForTablet(deviceSpecs["tablet"]!!, isGestureMode = false)
-        val dp = getDeviceProfileForGrid("6_by_5")
-        dp.isTaskbarPresentInApps = true
-
-        assertDump(dp, "tabletPortrait3Button")
-    }
-
-    @Test
-    fun tabletPortrait() {
-        initializeVarsForTablet(deviceSpecs["tablet"]!!)
-        val dp = getDeviceProfileForGrid("6_by_5")
-        dp.isTaskbarPresentInApps = true
-
-        assertDump(dp, "tabletPortrait")
-    }
-
-    @Test
-    fun twoPanelLandscape3Button() {
-        initializeVarsForTwoPanel(
-            deviceSpecs["twopanel-tablet"]!!,
-            deviceSpecs["twopanel-phone"]!!,
-            isLandscape = true,
-            isGestureMode = false
-        )
-        val dp = getDeviceProfileForGrid("4_by_4")
-        dp.isTaskbarPresentInApps = true
-
-        assertDump(dp, "twoPanelLandscape3Button")
-    }
-
-    @Test
-    fun twoPanelLandscape() {
-        initializeVarsForTwoPanel(
-            deviceSpecs["twopanel-tablet"]!!,
-            deviceSpecs["twopanel-phone"]!!,
-            isLandscape = true
-        )
-        val dp = getDeviceProfileForGrid("4_by_4")
-        dp.isTaskbarPresentInApps = true
-
-        assertDump(dp, "twoPanelLandscape")
-    }
-
-    @Test
-    fun twoPanelPortrait3Button() {
-        initializeVarsForTwoPanel(
-            deviceSpecs["twopanel-tablet"]!!,
-            deviceSpecs["twopanel-phone"]!!,
-            isGestureMode = false
-        )
-        val dp = getDeviceProfileForGrid("4_by_4")
-        dp.isTaskbarPresentInApps = true
-
-        assertDump(dp, "twoPanelPortrait3Button")
-    }
-
-    @Test
-    fun twoPanelPortrait() {
-        initializeVarsForTwoPanel(deviceSpecs["twopanel-tablet"]!!, deviceSpecs["twopanel-phone"]!!)
-        val dp = getDeviceProfileForGrid("4_by_4")
-        dp.isTaskbarPresentInApps = true
-
-        assertDump(dp, "twoPanelPortrait")
+    private fun initializeDevice(deviceName: String, isGestureMode: Boolean, isLandscape: Boolean) {
+        val deviceSpec = deviceSpecs[instance.deviceName]!!
+        when (deviceName) {
+            "twopanel-phone",
+            "twopanel-tablet" ->
+                initializeVarsForTwoPanel(
+                    deviceSpecUnfolded = deviceSpecs["twopanel-tablet"]!!,
+                    deviceSpecFolded = deviceSpecs["twopanel-phone"]!!,
+                    isLandscape = isLandscape,
+                    isGestureMode = isGestureMode,
+                )
+            "tablet" ->
+                initializeVarsForTablet(
+                    deviceSpec = deviceSpec,
+                    isLandscape = isLandscape,
+                    isGestureMode = isGestureMode
+                )
+            else ->
+                initializeVarsForPhone(
+                    deviceSpec = deviceSpec,
+                    isVerticalBar = isLandscape,
+                    isGestureMode = isGestureMode
+                )
+        }
     }
 
     private fun getDeviceProfileForGrid(gridName: String): DeviceProfile {
@@ -153,6 +121,48 @@
     }
 
     private fun assertDump(dp: DeviceProfile, filename: String) {
-        assertDump(dp, folderName, filename);
+        assertDump(dp, folderName, filename)
+    }
+
+    companion object {
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getInstances(): List<TestCase> {
+            return listOf(
+                TestCase("phone", gridName = "5_by_5"),
+                TestCase("tablet", gridName = "6_by_5", isTaskbarPresentInApps = true),
+                TestCase("twopanel-tablet", gridName = "4_by_4", isTaskbarPresentInApps = true),
+                TestCase(
+                    "twopanel-tablet",
+                    gridName = "4_by_4",
+                    isTaskbarPresentInApps = true,
+                    decoupleDepth = true
+                ),
+            )
+        }
+
+        data class TestCase(
+            val deviceName: String,
+            val gridName: String,
+            val isTaskbarPresentInApps: Boolean = false,
+            val decoupleDepth: Boolean = false
+        ) {
+            fun filename(testName: String = ""): String {
+                val device =
+                    when (deviceName) {
+                        "tablet" -> "tablet"
+                        "twopanel-tablet" -> "twoPanel"
+                        "twopanel-phone" -> "twoPanelFolded"
+                        else -> "phone"
+                    }
+                val depth =
+                    if (decoupleDepth) {
+                        "_decoupleDepth"
+                    } else {
+                        ""
+                    }
+                return "$device$testName$depth"
+            }
+        }
     }
 }
diff --git a/tests/src/com/android/launcher3/popup/SystemShortcutTest.java b/tests/src/com/android/launcher3/popup/SystemShortcutTest.java
index dc8c17a..c663be0 100644
--- a/tests/src/com/android/launcher3/popup/SystemShortcutTest.java
+++ b/tests/src/com/android/launcher3/popup/SystemShortcutTest.java
@@ -57,6 +57,7 @@
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.pm.UserCache;
+import com.android.launcher3.util.ApiWrapper;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.LauncherModelHelper.SandboxModelContext;
 import com.android.launcher3.util.TestSandboxModelContextWrapper;
@@ -88,6 +89,7 @@
     private PopupDataProvider mPopupDataProvider;
     private AppInfo mAppInfo;
     @Mock UserCache mUserCache;
+    @Mock ApiWrapper mApiWrapper;
     @Mock BaseDragLayer mBaseDragLayer;
     @Mock UserIconInfo mUserIconInfo;
     @Mock LauncherActivityInfo mLauncherActivityInfo;
@@ -98,6 +100,7 @@
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         mSandboxContext.putObject(UserCache.INSTANCE, mUserCache);
+        mSandboxContext.putObject(ApiWrapper.INSTANCE, mApiWrapper);
         mTestContext = new TestSandboxModelContextWrapper(mSandboxContext);
         mView = new View(mSandboxContext);
         spyOn(mTestContext);