Convert DesktopTaskView to Kotlin

Bug: 339787593
Test: Manual
Flag: None
Change-Id: I94ee4675c11ef64c12b0e3278d4af367b764375e
diff --git a/quickstep/src/com/android/quickstep/views/DesktopTaskView.java b/quickstep/src/com/android/quickstep/views/DesktopTaskView.java
deleted file mode 100644
index 1bedad4..0000000
--- a/quickstep/src/com/android/quickstep/views/DesktopTaskView.java
+++ /dev/null
@@ -1,433 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.quickstep.views;
-
-import  static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
-
-import static com.android.launcher3.LauncherState.BACKGROUND_APP;
-import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_UNDEFINED;
-
-import android.content.Context;
-import android.graphics.Point;
-import android.graphics.PointF;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.LayerDrawable;
-import android.graphics.drawable.ShapeDrawable;
-import android.graphics.drawable.shapes.RoundRectShape;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.View;
-import android.widget.FrameLayout;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.desktop.DesktopRecentsTransitionController;
-import com.android.launcher3.util.CancellableTask;
-import com.android.launcher3.util.RunnableList;
-import com.android.launcher3.util.ViewPool;
-import com.android.quickstep.BaseContainerInterface;
-import com.android.quickstep.RecentsModel;
-import com.android.quickstep.TaskThumbnailCache;
-import com.android.quickstep.util.RecentsOrientedState;
-import com.android.systemui.shared.recents.model.Task;
-import com.android.systemui.shared.recents.model.ThumbnailData;
-import com.android.systemui.shared.system.QuickStepContract;
-
-import kotlin.Unit;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.function.Consumer;
-
-/**
- * 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.
-public class DesktopTaskView extends TaskView {
-
-    private static final String TAG = "DesktopTaskView";
-
-    private static final boolean DEBUG = false;
-
-    private final ArrayList<CancellableTask<?>> mPendingThumbnailRequests = new ArrayList<>();
-
-    private final TaskView.FullscreenDrawParams mSnapshotDrawParams;
-
-    private View mBackgroundView;
-
-    private int mChildCountAtInflation;
-
-    private final PointF mTempPointF = new PointF();
-
-    private final ViewPool<TaskThumbnailViewDeprecated> mTaskthumbnailViewPool;
-
-    public DesktopTaskView(Context context) {
-        this(context, null);
-    }
-
-    public DesktopTaskView(Context context, AttributeSet attrs) {
-        this(context, attrs, 0);
-    }
-
-    public DesktopTaskView(Context context, AttributeSet attrs, int defStyleAttr) {
-        super(context, attrs, defStyleAttr);
-
-        mSnapshotDrawParams = new FullscreenDrawParams(context) {
-            @Override
-            public float computeTaskCornerRadius(Context context) {
-                return QuickStepContract.getWindowCornerRadius(context);
-            }
-
-            @Override
-            public float computeWindowCornerRadius(Context context) {
-                return QuickStepContract.getWindowCornerRadius(context);
-            }
-        };
-        // As DesktopTaskView is inflated in background, use initialSize=0 to avoid initPool.
-        mTaskthumbnailViewPool = new ViewPool<>(context, this, R.layout.task_thumbnail,
-                /* maxSize= */10, /* initialSize= */ 0);
-        mTaskContainers = new ArrayList<>();
-    }
-
-    @Override
-    protected void onFinishInflate() {
-        super.onFinishInflate();
-
-        mBackgroundView = findViewById(R.id.background);
-
-        int topMarginPx =
-                mContainer.getDeviceProfile().overviewTaskThumbnailTopMarginPx;
-        FrameLayout.LayoutParams params = (LayoutParams) mBackgroundView.getLayoutParams();
-        params.topMargin = topMarginPx;
-        mBackgroundView.setLayoutParams(params);
-
-        float[] outerRadii = new float[8];
-        Arrays.fill(outerRadii, getTaskCornerRadius());
-        RoundRectShape shape = new RoundRectShape(outerRadii, null, null);
-        ShapeDrawable background = new ShapeDrawable(shape);
-        background.setTint(getResources().getColor(android.R.color.system_neutral2_300,
-                getContext().getTheme()));
-        mBackgroundView.setBackground(background);
-
-        Drawable icon = getResources().getDrawable(R.drawable.ic_desktop, getContext().getTheme());
-        Drawable iconBackground = getResources().getDrawable(R.drawable.bg_circle,
-                getContext().getTheme());
-        setIcon(mIconView, new LayerDrawable(new Drawable[]{iconBackground, icon}));
-
-        mChildCountAtInflation = getChildCount();
-    }
-
-    @Override
-    public Unit getThumbnailBounds(@NonNull Rect bounds, boolean relativeToDragLayer) {
-        if (relativeToDragLayer) {
-            mContainer.getDragLayer().getDescendantRectRelativeToSelf(mBackgroundView, bounds);
-        } else {
-            bounds.set(mBackgroundView.getLeft(), mBackgroundView.getTop(),
-                    mBackgroundView.getRight(), mBackgroundView.getBottom());
-        }
-        return Unit.INSTANCE;
-    }
-
-    @Override
-    public void bind(Task task, RecentsOrientedState orientedState) {
-        bind(Collections.singletonList(task), orientedState);
-    }
-
-    /**
-     * Updates this desktop task to the gives task list defined in {@code tasks}
-     */
-    public void bind(List<Task> tasks, RecentsOrientedState orientedState) {
-        if (DEBUG) {
-            StringBuilder sb = new StringBuilder();
-            sb.append("bind tasks=").append(tasks.size()).append("\n");
-            for (Task task : tasks) {
-                sb.append(" key=").append(task.key).append("\n");
-            }
-            Log.d(TAG, sb.toString());
-        }
-        cancelPendingLoadTasks();
-
-        ((ArrayList<TaskContainer>) mTaskContainers).ensureCapacity(tasks.size());
-        for (int i = 0; i < tasks.size(); i++) {
-            Task task = tasks.get(i);
-            TaskThumbnailViewDeprecated thumbnailView;
-            if (i >= mTaskContainers.size()) {
-                thumbnailView = mTaskthumbnailViewPool.getView();
-                // Add thumbnailView from to position after the initial child views.
-                addView(thumbnailView, mChildCountAtInflation,
-                        new LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
-            } else {
-                thumbnailView = mTaskContainers.get(i).getThumbnailView();
-            }
-            thumbnailView.bind(task);
-            TaskContainer taskContainer = new TaskContainer(task, thumbnailView, mIconView,
-                    STAGE_POSITION_UNDEFINED, /*digitalWellBeingToast=*/ null);
-            if (i >= mTaskContainers.size()) {
-                mTaskContainers.add(taskContainer);
-            } else {
-                mTaskContainers.set(i, taskContainer);
-            }
-        }
-        while (mTaskContainers.size() > tasks.size()) {
-            TaskContainer taskContainer = mTaskContainers.remove(mTaskContainers.size() - 1);
-            removeView(taskContainer.getThumbnailView());
-            mTaskthumbnailViewPool.recycle(taskContainer.getThumbnailView());
-        }
-
-        setOrientationState(orientedState);
-    }
-
-    @Override
-    public void onTaskListVisibilityChanged(boolean visible, int changes) {
-        cancelPendingLoadTasks();
-        if (visible) {
-            RecentsModel model = RecentsModel.INSTANCE.get(getContext());
-            TaskThumbnailCache thumbnailCache = model.getThumbnailCache();
-
-            if (needsUpdate(changes, FLAG_UPDATE_THUMBNAIL)) {
-                for (TaskContainer container : mTaskContainers) {
-                    CancellableTask<?> thumbLoadRequest =
-                            thumbnailCache.updateThumbnailInBackground(container.getTask(),
-                                    thumbnailData -> container.getThumbnailView().setThumbnail(
-                                            container.getTask(),
-                                            thumbnailData));
-                    if (thumbLoadRequest != null) {
-                        mPendingThumbnailRequests.add(thumbLoadRequest);
-                    }
-                }
-            }
-        } else {
-            if (needsUpdate(changes, FLAG_UPDATE_THUMBNAIL)) {
-                for (TaskContainer container : mTaskContainers) {
-                    container.getThumbnailView().setThumbnail(null, null);
-                    // Reset the task thumbnail ref
-                    container.getTask().thumbnail = null;
-                }
-            }
-        }
-    }
-
-    @Override
-    protected void setThumbnailOrientation(RecentsOrientedState orientationState) {
-        // no-op
-    }
-
-    @Override
-    protected void cancelPendingLoadTasks() {
-        for (CancellableTask<?> cancellableTask : mPendingThumbnailRequests) {
-            cancellableTask.cancel();
-        }
-        mPendingThumbnailRequests.clear();
-    }
-
-    @Nullable
-    @Override
-    public RunnableList launchTaskAnimated() {
-        RunnableList endCallback = new RunnableList();
-
-        RecentsView recentsView = getRecentsView();
-        DesktopRecentsTransitionController recentsController =
-                recentsView.getDesktopRecentsController();
-        if (recentsController != null) {
-            recentsController.launchDesktopFromRecents(this,
-                    success -> endCallback.executeAllAndDestroy());
-            Log.d(TAG, "launchTaskAnimated - launchDesktopFromRecents: " + Arrays.toString(
-                    getTaskIds()));
-        } else {
-            Log.d(TAG, "launchTaskAnimated - recentsController is null: " + Arrays.toString(
-                    getTaskIds()));
-        }
-
-        // Callbacks get run from recentsView for case when recents animation already running
-        recentsView.addSideTaskLaunchCallback(endCallback);
-        return endCallback;
-    }
-
-    @Override
-    public void launchTask(@NonNull Consumer<Boolean> callback, boolean isQuickswitch) {
-        launchTasks();
-        callback.accept(true);
-    }
-
-    @Override
-    void refreshThumbnails(@Nullable HashMap<Integer, ThumbnailData> thumbnailDatas) {
-        // Sets new thumbnails based on the incoming data and refreshes the rest.
-        if (thumbnailDatas != null) {
-            for (TaskContainer container : mTaskContainers) {
-                ThumbnailData thumbnailData = thumbnailDatas.get(container.getTask().key.id);
-                if (thumbnailData != null) {
-                    container.getThumbnailView().setThumbnail(container.getTask(), thumbnailData);
-                } else {
-                    // Refresh the rest that were not updated.
-                    container.getThumbnailView().refresh();
-                }
-            }
-        }
-    }
-
-    @Override
-    public void onRecycle() {
-        resetPersistentViewTransforms();
-        // Clear any references to the thumbnail (it will be re-read either from the cache or the
-        // system on next bind)
-        for (TaskContainer container : mTaskContainers) {
-            container.getThumbnailView().setThumbnail(container.getTask(), null);
-        }
-        setOverlayEnabled(false);
-        onTaskListVisibilityChanged(false);
-        setVisibility(VISIBLE);
-    }
-
-    @Override
-    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-        int containerWidth = MeasureSpec.getSize(widthMeasureSpec);
-        int containerHeight = MeasureSpec.getSize(heightMeasureSpec);
-
-        setMeasuredDimension(containerWidth, containerHeight);
-
-        int thumbnailTopMarginPx = mContainer.getDeviceProfile().overviewTaskThumbnailTopMarginPx;
-        containerHeight -= thumbnailTopMarginPx;
-
-        if (mTaskContainers.isEmpty()) {
-            return;
-        }
-
-        BaseContainerInterface.getTaskDimension(mContext, mContainer.getDeviceProfile(),
-                mTempPointF);
-        int windowWidth = (int) mTempPointF.x;
-        int windowHeight = (int) mTempPointF.y;
-
-        float scaleWidth = containerWidth / (float) windowWidth;
-        float scaleHeight = containerHeight / (float) windowHeight;
-
-        if (DEBUG) {
-            Log.d(TAG,
-                    "onMeasure: container=[" + containerWidth + "," + containerHeight + "] window=["
-                            + windowWidth + "," + windowHeight + "] scale=[" + scaleWidth + ","
-                            + scaleHeight + "]");
-        }
-
-        // Desktop tile is a shrunk down version of launcher and freeform task thumbnails.
-        for (TaskContainer container : mTaskContainers) {
-            Task task = container.getTask();
-            Rect taskSize = task.appBounds;
-            if (taskSize == null) {
-                // Default to quarter of the desktop if we did not get app bounds.
-                taskSize = new Rect(0, 0, windowWidth / 4, windowHeight / 4);
-            }
-
-            int thumbWidth = (int) (taskSize.width() * scaleWidth);
-            int thumbHeight = (int) (taskSize.height() * scaleHeight);
-
-            TaskThumbnailViewDeprecated thumbnailView = container.getThumbnailView();
-            if (thumbnailView != null) {
-                thumbnailView.measure(MeasureSpec.makeMeasureSpec(thumbWidth, MeasureSpec.EXACTLY),
-                        MeasureSpec.makeMeasureSpec(thumbHeight, MeasureSpec.EXACTLY));
-
-                // Position the task to the same position as it would be on the desktop
-                Point positionInParent = task.positionInParent;
-                if (positionInParent == null) {
-                    positionInParent = new Point(0, 0);
-                }
-                int taskX = (int) (positionInParent.x * scaleWidth);
-                int taskY = (int) (positionInParent.y * scaleHeight);
-                // move task down by margin size
-                taskY += thumbnailTopMarginPx;
-                thumbnailView.setX(taskX);
-                thumbnailView.setY(taskY);
-
-                if (DEBUG) {
-                    Log.d(TAG, "onMeasure: task=" + task.key + " thumb=[" + thumbWidth + ","
-                            + thumbHeight + "]" + " pos=[" + taskX + "," + taskY + "]");
-                }
-            }
-        }
-    }
-
-    @Override
-    public void setOverlayEnabled(boolean overlayEnabled) {
-        // TODO(b/330685808) support overlay for Screenshot action
-    }
-
-    @Override
-    public void setFullscreenProgress(float progress) {
-        // TODO(b/249371338): this copies parent implementation and makes it work for N thumbs
-        progress = Utilities.boundToRange(progress, 0, 1);
-        mFullscreenProgress = progress;
-        mIconView.setVisibility(progress < 1 ? VISIBLE : INVISIBLE);
-        if (mFullscreenProgress > 0) {
-            // Don't show background while we are transitioning to/from fullscreen
-            mBackgroundView.setVisibility(INVISIBLE);
-        } else {
-            mBackgroundView.setVisibility(VISIBLE);
-        }
-        for (TaskContainer container : mTaskContainers) {
-            container.getThumbnailView().getTaskOverlay().setFullscreenProgress(progress);
-        }
-        // Animate icons and DWB banners in/out, except in QuickSwitch state, when tiles are
-        // oversized and banner would look disproportionately large.
-        if (mContainer.<RecentsView<?, ?>>getOverviewPanel().getStateManager().getState()
-                != BACKGROUND_APP) {
-            setIconsAndBannersTransitionProgress(progress, true);
-        }
-        updateSnapshotRadius();
-    }
-
-    @Override
-    protected void updateSnapshotRadius() {
-        super.updateSnapshotRadius();
-        updateFullscreenParams(mSnapshotDrawParams);
-        for (TaskContainer container : mTaskContainers) {
-            container.getThumbnailView().setFullscreenParams(mSnapshotDrawParams);
-        }
-    }
-
-    @Override
-    public void setColorTint(float amount, int tintColor) {
-        for (TaskContainer container : mTaskContainers) {
-            container.getThumbnailView().setDimAlpha(amount);
-        }
-    }
-
-    @Override
-    protected void applyThumbnailSplashAlpha() {
-        for (TaskContainer container : mTaskContainers) {
-            container.getThumbnailView().setSplashAlpha(mTaskThumbnailSplashAlpha);
-        }
-    }
-
-    @Override
-    void setThumbnailVisibility(int visibility, int taskId) {
-        for (TaskContainer container : mTaskContainers) {
-            container.getThumbnailView().setVisibility(visibility);
-        }
-    }
-
-    @Override
-    protected boolean confirmSecondSplitSelectApp() {
-        // Desktop tile can't be in split screen
-        return false;
-    }
-}
diff --git a/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt b/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt
new file mode 100644
index 0000000..b7c0236
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt
@@ -0,0 +1,362 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep.views
+
+import android.content.Context
+import android.graphics.Point
+import android.graphics.PointF
+import android.graphics.Rect
+import android.graphics.drawable.LayerDrawable
+import android.graphics.drawable.ShapeDrawable
+import android.graphics.drawable.shapes.RoundRectShape
+import android.util.AttributeSet
+import android.util.Log
+import android.view.View
+import android.view.ViewGroup
+import androidx.core.view.updateLayoutParams
+import com.android.launcher3.LauncherState
+import com.android.launcher3.R
+import com.android.launcher3.Utilities
+import com.android.launcher3.util.CancellableTask
+import com.android.launcher3.util.RunnableList
+import com.android.launcher3.util.SplitConfigurationOptions
+import com.android.launcher3.util.ViewPool
+import com.android.quickstep.BaseContainerInterface
+import com.android.quickstep.RecentsModel
+import com.android.quickstep.util.RecentsOrientedState
+import com.android.systemui.shared.recents.model.Task
+import com.android.systemui.shared.recents.model.ThumbnailData
+import com.android.systemui.shared.system.QuickStepContract
+import java.util.function.Consumer
+
+/** 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) {
+            override fun computeTaskCornerRadius(context: Context) =
+                QuickStepContract.getWindowCornerRadius(context)
+
+            override fun computeWindowCornerRadius(context: Context) =
+                QuickStepContract.getWindowCornerRadius(context)
+        }
+    private val taskThumbnailViewPool =
+        ViewPool<TaskThumbnailViewDeprecated>(
+            context,
+            this,
+            R.layout.task_thumbnail,
+            10,
+            0 // As DesktopTaskView is inflated in background, use initialSize=0 to avoid initPool.
+        )
+    private val tempPointF = PointF()
+    private val tempRect = Rect()
+    private lateinit var backgroundView: View
+    private var childCountAtInflation = 0
+
+    init {
+        mTaskContainers = ArrayList()
+    }
+
+    override fun onFinishInflate() {
+        super.onFinishInflate()
+
+        backgroundView = findViewById(R.id.background)!!
+        val topMarginPx = mContainer.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))
+            }
+
+        val iconBackground = resources.getDrawable(R.drawable.bg_circle, context.theme)
+        val icon = resources.getDrawable(R.drawable.ic_desktop, context.theme)
+        setIcon(mIconView, LayerDrawable(arrayOf(iconBackground, icon)))
+
+        childCountAtInflation = childCount
+    }
+
+    override fun getThumbnailBounds(bounds: Rect, relativeToDragLayer: Boolean) {
+        if (relativeToDragLayer) {
+            mContainer.dragLayer.getDescendantRectRelativeToSelf(backgroundView, bounds)
+        } else {
+            bounds.set(
+                backgroundView.left,
+                backgroundView.top,
+                backgroundView.right,
+                backgroundView.bottom
+            )
+        }
+    }
+
+    override fun bind(task: Task, orientedState: RecentsOrientedState) {
+        bind(listOf(task), orientedState)
+    }
+
+    /** Updates this desktop task to the gives task list defined in `tasks` */
+    fun bind(tasks: List<Task>, orientedState: RecentsOrientedState) {
+        if (DEBUG) {
+            val sb = StringBuilder()
+            sb.append("bind tasks=").append(tasks.size).append("\n")
+            tasks.forEach { sb.append(" key=${it.key}\n") }
+            Log.d(TAG, sb.toString())
+        }
+        cancelPendingLoadTasks()
+
+        (mTaskContainers as ArrayList).ensureCapacity(tasks.size)
+        tasks.forEachIndexed { index, task ->
+            val thumbnailView: TaskThumbnailViewDeprecated
+            if (index >= mTaskContainers.size) {
+                thumbnailView = taskThumbnailViewPool.view
+                // Add thumbnailView from to position after the initial child views.
+                addView(
+                    thumbnailView,
+                    childCountAtInflation,
+                    LayoutParams(
+                        ViewGroup.LayoutParams.WRAP_CONTENT,
+                        ViewGroup.LayoutParams.WRAP_CONTENT
+                    )
+                )
+            } else {
+                thumbnailView = mTaskContainers[index].thumbnailView
+            }
+            thumbnailView.bind(task)
+            val taskContainer =
+                TaskContainer(
+                    task,
+                    thumbnailView,
+                    mIconView,
+                    SplitConfigurationOptions.STAGE_POSITION_UNDEFINED,
+                    null
+                )
+            if (index >= mTaskContainers.size) {
+                mTaskContainers.add(taskContainer)
+            } else {
+                mTaskContainers[index] = taskContainer
+            }
+        }
+        while (mTaskContainers.size > tasks.size) {
+            mTaskContainers.removeLast().apply {
+                removeView(thumbnailView)
+                taskThumbnailViewPool.recycle(thumbnailView)
+            }
+        }
+
+        setOrientationState(orientedState)
+    }
+
+    override fun onTaskListVisibilityChanged(visible: Boolean, changes: Int) {
+        cancelPendingLoadTasks()
+        if (needsUpdate(changes, FLAG_UPDATE_THUMBNAIL)) {
+            mTaskContainers.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
+                }
+            }
+        }
+    }
+
+    // thumbnailView is laid out differently and is handled in onMeasure
+    override fun setThumbnailOrientation(orientationState: RecentsOrientedState) {}
+
+    override fun cancelPendingLoadTasks() {
+        pendingThumbnailRequests.forEach { it.cancel() }
+        pendingThumbnailRequests.clear()
+    }
+
+    override fun launchTaskAnimated(): RunnableList? {
+        val recentsView = recentsView
+        if (recentsView == null) {
+            Log.d(TAG, "launchTaskAnimated - recentsView is null")
+            return null
+        }
+
+        val endCallback = RunnableList()
+        val desktopController = recentsView.desktopRecentsController
+        if (desktopController == null) {
+            Log.d(
+                TAG,
+                "launchTaskAnimated - recentsController is null: ${taskIds.contentToString()}"
+            )
+        } else {
+            desktopController.launchDesktopFromRecents(this) { endCallback.executeAllAndDestroy() }
+            Log.d(
+                TAG,
+                "launchTaskAnimated - launchDesktopFromRecents: ${taskIds.contentToString()}"
+            )
+        }
+
+        // Callbacks get run from recentsView for case when recents animation already running
+        recentsView.addSideTaskLaunchCallback(endCallback)
+        return endCallback
+    }
+
+    override fun launchTask(callback: Consumer<Boolean>, isQuickswitch: Boolean) {
+        launchTasks()
+        callback.accept(true)
+    }
+
+    public override fun refreshThumbnails(thumbnailDatas: HashMap<Int, ThumbnailData>?) {
+        // Sets new thumbnails based on the incoming data and refreshes the rest.
+        thumbnailDatas?.let {
+            mTaskContainers.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()
+                }
+            }
+        }
+    }
+
+    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)
+        mTaskContainers.forEach { it.thumbnailView.setThumbnail(it.task, null) }
+        setOverlayEnabled(false)
+        onTaskListVisibilityChanged(false)
+        visibility = VISIBLE
+    }
+
+    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
+        val containerWidth = MeasureSpec.getSize(widthMeasureSpec)
+        var containerHeight = MeasureSpec.getSize(heightMeasureSpec)
+        setMeasuredDimension(containerWidth, containerHeight)
+
+        if (mTaskContainers.isEmpty()) {
+            return
+        }
+
+        val thumbnailTopMarginPx = mContainer.deviceProfile.overviewTaskThumbnailTopMarginPx
+        containerHeight -= thumbnailTopMarginPx
+
+        BaseContainerInterface.getTaskDimension(mContext, mContainer.deviceProfile, tempPointF)
+        val windowWidth = tempPointF.x.toInt()
+        val windowHeight = tempPointF.y.toInt()
+        val scaleWidth = containerWidth / windowWidth.toFloat()
+        val scaleHeight = containerHeight / windowHeight.toFloat()
+        if (DEBUG) {
+            Log.d(
+                TAG,
+                "onMeasure: container=[$containerWidth,$containerHeight] " +
+                    "window=[$windowWidth,$windowHeight] scale=[$scaleWidth,$scaleHeight]"
+            )
+        }
+
+        // Desktop tile is a shrunk down version of launcher and freeform task thumbnails.
+        mTaskContainers.forEach {
+            // Default to quarter of the desktop if we did not get app bounds.
+            val taskSize =
+                it.task.appBounds
+                    ?: tempRect.apply {
+                        left = 0
+                        top = 0
+                        right = windowWidth / 4
+                        bottom = windowHeight / 4
+                    }
+            val thumbWidth = (taskSize.width() * scaleWidth).toInt()
+            val thumbHeight = (taskSize.height() * scaleHeight).toInt()
+            it.thumbnailView.measure(
+                MeasureSpec.makeMeasureSpec(thumbWidth, MeasureSpec.EXACTLY),
+                MeasureSpec.makeMeasureSpec(thumbHeight, MeasureSpec.EXACTLY)
+            )
+
+            // Position the task to the same position as it would be on the desktop
+            val positionInParent = it.task.positionInParent ?: ORIGIN
+            val taskX = (positionInParent.x * scaleWidth).toInt()
+            var taskY = (positionInParent.y * scaleHeight).toInt()
+            // move task down by margin size
+            taskY += thumbnailTopMarginPx
+            it.thumbnailView.x = taskX.toFloat()
+            it.thumbnailView.y = taskY.toFloat()
+            if (DEBUG) {
+                Log.d(
+                    TAG,
+                    "onMeasure: task=${it.task.key} thumb=[$thumbWidth,$thumbHeight]" +
+                        " pos=[$taskX,$taskY]"
+                )
+            }
+        }
+    }
+
+    // TODO(b/330685808) support overlay for Screenshot action
+    override fun setOverlayEnabled(overlayEnabled: Boolean) {}
+
+    override fun setFullscreenProgress(progress: Float) {
+        // TODO(b/249371338): this copies parent implementation and makes it work for N thumbs
+        val boundProgress = Utilities.boundToRange(progress, 0f, 1f)
+        mFullscreenProgress = boundProgress
+        mIconView.setVisibility(if (boundProgress < 1) VISIBLE else INVISIBLE)
+        // Don't show background while we are transitioning to/from fullscreen
+        backgroundView.visibility = if (mFullscreenProgress > 0) INVISIBLE else VISIBLE
+        mTaskContainers.forEach {
+            it.thumbnailView.taskOverlay.setFullscreenProgress(boundProgress)
+        }
+        // Animate icons and DWB banners in/out, except in QuickSwitch state, when tiles are
+        // oversized and banner would look disproportionately large.
+        if (
+            mContainer.getOverviewPanel<RecentsView<*, *>>().getStateManager().state !=
+                LauncherState.BACKGROUND_APP
+        ) {
+            setIconsAndBannersTransitionProgress(boundProgress, true)
+        }
+        updateSnapshotRadius()
+    }
+
+    override fun updateSnapshotRadius() {
+        super.updateSnapshotRadius()
+        updateFullscreenParams(snapshotDrawParams)
+        mTaskContainers.forEach { it.thumbnailView.setFullscreenParams(snapshotDrawParams) }
+    }
+
+    override fun setColorTint(amount: Float, tintColor: Int) {
+        mTaskContainers.forEach { it.thumbnailView.dimAlpha = amount }
+    }
+
+    override fun applyThumbnailSplashAlpha() {
+        mTaskContainers.forEach { it.thumbnailView.setSplashAlpha(mTaskThumbnailSplashAlpha) }
+    }
+
+    public override fun setThumbnailVisibility(visibility: Int, taskId: Int) {
+        mTaskContainers.forEach { it.thumbnailView.visibility = visibility }
+    }
+
+    // Desktop tile can't be in split screen
+    override fun confirmSecondSplitSelectApp(): Boolean = false
+
+    companion object {
+        private const val TAG = "DesktopTaskView"
+        private const val DEBUG = true
+        private val ORIGIN = Point(0, 0)
+    }
+}