diff --git a/quickstep/res/layout/task_desktop.xml b/quickstep/res/layout/task_desktop.xml
new file mode 100644
index 0000000000..0c8543f414
--- /dev/null
+++ b/quickstep/res/layout/task_desktop.xml
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 006628cc9e..07ddcc8b31 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -1882,6 +1882,7 @@ public abstract class AbsSwipeUpHandler,
}
private void finishCurrentTransitionToRecents() {
+ // TODO(b/245569277#comment2): enable once isFreeformActive is implemented
mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED);
if (mRecentsAnimationController != null) {
mRecentsAnimationController.detachNavigationBarFromApp(true);
diff --git a/quickstep/src/com/android/quickstep/RecentTasksList.java b/quickstep/src/com/android/quickstep/RecentTasksList.java
index 6b616b1b07..7bcc661af7 100644
--- a/quickstep/src/com/android/quickstep/RecentTasksList.java
+++ b/quickstep/src/com/android/quickstep/RecentTasksList.java
@@ -17,6 +17,8 @@
package com.android.quickstep;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+import static com.android.quickstep.views.DesktopTaskView.DESKTOP_MODE_SUPPORTED;
+import static com.android.wm.shell.util.GroupedRecentTaskInfo.TYPE_FREEFORM;
import android.annotation.TargetApi;
import android.app.ActivityManager;
@@ -30,6 +32,7 @@ import androidx.annotation.VisibleForTesting;
import com.android.launcher3.util.LooperExecutor;
import com.android.launcher3.util.SplitConfigurationOptions;
+import com.android.quickstep.util.DesktopTask;
import com.android.quickstep.util.GroupTask;
import com.android.systemui.shared.recents.model.Task;
import com.android.wm.shell.recents.IRecentTasksListener;
@@ -253,8 +256,9 @@ public class RecentTasksList {
};
TaskLoadResult allTasks = new TaskLoadResult(requestId, loadKeysOnly, rawTasks.size());
+
for (GroupedRecentTaskInfo rawTask : rawTasks) {
- if (rawTask.getType() == GroupedRecentTaskInfo.TYPE_FREEFORM) {
+ if (DESKTOP_MODE_SUPPORTED && rawTask.getType() == TYPE_FREEFORM) {
GroupTask desktopTask = createDesktopTask(rawTask);
allTasks.add(desktopTask);
continue;
@@ -284,14 +288,18 @@ public class RecentTasksList {
return allTasks;
}
- private GroupTask createDesktopTask(GroupedRecentTaskInfo taskInfo) {
- // TODO(b/244348395): create a subclass of GroupTask for desktop tile
- // We need a single task information as the primary task. Use the first task
- Task.TaskKey key = new Task.TaskKey(taskInfo.getTaskInfo1());
- Task task = new Task(key);
- task.desktopTile = true;
- task.topActivity = key.sourceComponent;
- return new GroupTask(task, null, null);
+ private DesktopTask createDesktopTask(GroupedRecentTaskInfo recentTaskInfo) {
+ ArrayList tasks = new ArrayList<>(recentTaskInfo.getTaskInfoList().size());
+ for (ActivityManager.RecentTaskInfo taskInfo : recentTaskInfo.getTaskInfoList()) {
+ Task.TaskKey key = new Task.TaskKey(taskInfo);
+ Task task = Task.from(key, taskInfo, false);
+ task.setLastSnapshotData(taskInfo);
+ task.positionInParent = taskInfo.positionInParent;
+ task.appBounds = taskInfo.configuration.windowConfiguration.getAppBounds();
+ // TODO(b/244348395): tasks should be sorted from oldest to most recently used
+ tasks.add(task);
+ }
+ return new DesktopTask(tasks);
}
private SplitConfigurationOptions.SplitBounds convertSplitBounds(
@@ -306,7 +314,7 @@ public class RecentTasksList {
private ArrayList copyOf(ArrayList tasks) {
ArrayList newTasks = new ArrayList<>();
for (int i = 0; i < tasks.size(); i++) {
- newTasks.add(new GroupTask(tasks.get(i)));
+ newTasks.add(tasks.get(i).copy());
}
return newTasks;
}
diff --git a/quickstep/src/com/android/quickstep/TaskViewUtils.java b/quickstep/src/com/android/quickstep/TaskViewUtils.java
index 7d3d8a2ede..df80e2f867 100644
--- a/quickstep/src/com/android/quickstep/TaskViewUtils.java
+++ b/quickstep/src/com/android/quickstep/TaskViewUtils.java
@@ -302,9 +302,15 @@ public final class TaskViewUtils {
// to follow the TaskViewSimulator. So the final matrix applied on the thumbnailView is:
// Mt K(0)` K(t) Mt`
TaskThumbnailView[] thumbnails = v.getThumbnails();
- Matrix[] mt = new Matrix[simulatorCopies.length];
- Matrix[] mti = new Matrix[simulatorCopies.length];
- for (int i = 0; i < thumbnails.length; i++) {
+
+ // In case simulator copies and thumbnail size do no match, ensure we get the lesser.
+ // This ensures we do not create arrays with empty elements or attempt to references
+ // indexes out of array bounds.
+ final int matrixSize = Math.min(simulatorCopies.length, thumbnails.length);
+
+ Matrix[] mt = new Matrix[matrixSize];
+ Matrix[] mti = new Matrix[matrixSize];
+ for (int i = 0; i < matrixSize; i++) {
TaskThumbnailView ttv = thumbnails[i];
RectF localBounds = new RectF(0, 0, ttv.getWidth(), ttv.getHeight());
float[] tvBoundsMapped = new float[]{0, 0, ttv.getWidth(), ttv.getHeight()};
@@ -321,14 +327,14 @@ public final class TaskViewUtils {
mti[i] = localMti;
}
- Matrix[] k0i = new Matrix[simulatorCopies.length];
- for (int i = 0; i < simulatorCopies.length; i++) {
+ Matrix[] k0i = new Matrix[matrixSize];
+ for (int i = 0; i < matrixSize; i++) {
k0i[i] = new Matrix();
simulatorCopies[i].getTaskViewSimulator().getCurrentMatrix().invert(k0i[i]);
}
Matrix animationMatrix = new Matrix();
out.addOnFrameCallback(() -> {
- for (int i = 0; i < simulatorCopies.length; i++) {
+ for (int i = 0; i < matrixSize; i++) {
animationMatrix.set(mt[i]);
animationMatrix.postConcat(k0i[i]);
animationMatrix.postConcat(simulatorCopies[i]
diff --git a/quickstep/src/com/android/quickstep/util/DesktopTask.java b/quickstep/src/com/android/quickstep/util/DesktopTask.java
new file mode 100644
index 0000000000..433d23fa97
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/DesktopTask.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.util;
+
+import com.android.quickstep.views.TaskView;
+import com.android.systemui.shared.recents.model.Task;
+
+import java.util.ArrayList;
+
+/**
+ * A {@link Task} container that can contain N number of tasks that are part of the desktop in
+ * recent tasks list.
+ */
+public class DesktopTask extends GroupTask {
+
+ public ArrayList tasks;
+
+ public DesktopTask(ArrayList tasks) {
+ super(tasks.get(0), null, null, TaskView.Type.DESKTOP);
+ this.tasks = tasks;
+ }
+
+ @Override
+ public boolean containsTask(int taskId) {
+ for (Task task : tasks) {
+ if (task.key.id == taskId) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean hasMultipleTasks() {
+ return true;
+ }
+
+ @Override
+ public DesktopTask copy() {
+ return new DesktopTask(tasks);
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/util/GroupTask.java b/quickstep/src/com/android/quickstep/util/GroupTask.java
index f30d00c722..2be4f0a519 100644
--- a/quickstep/src/com/android/quickstep/util/GroupTask.java
+++ b/quickstep/src/com/android/quickstep/util/GroupTask.java
@@ -20,6 +20,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.launcher3.util.SplitConfigurationOptions.SplitBounds;
+import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.recents.model.Task;
/**
@@ -27,24 +28,25 @@ import com.android.systemui.shared.recents.model.Task;
* are represented as an app-pair in the recents task list.
*/
public class GroupTask {
- public @NonNull Task task1;
- public @Nullable Task task2;
- public @Nullable
- SplitBounds mSplitBounds;
+ @NonNull
+ public final Task task1;
+ @Nullable
+ public final Task task2;
+ @Nullable
+ public final SplitBounds mSplitBounds;
+ @TaskView.Type
+ public final int taskViewType;
- public GroupTask(@NonNull Task t1, @Nullable Task t2,
- @Nullable SplitBounds splitBounds) {
+ public GroupTask(@NonNull Task t1, @Nullable Task t2, @Nullable SplitBounds splitBounds) {
+ this(t1, t2, splitBounds, t2 != null ? TaskView.Type.GROUPED : TaskView.Type.SINGLE);
+ }
+
+ protected GroupTask(@NonNull Task t1, @Nullable Task t2, @Nullable SplitBounds splitBounds,
+ @TaskView.Type int taskViewType) {
task1 = t1;
task2 = t2;
mSplitBounds = splitBounds;
- }
-
- public GroupTask(@NonNull GroupTask group) {
- task1 = new Task(group.task1);
- task2 = group.task2 != null
- ? new Task(group.task2)
- : null;
- mSplitBounds = group.mSplitBounds;
+ this.taskViewType = taskViewType;
}
public boolean containsTask(int taskId) {
@@ -54,4 +56,14 @@ public class GroupTask {
public boolean hasMultipleTasks() {
return task2 != null;
}
+
+ /**
+ * Create a copy of this instance
+ */
+ public GroupTask copy() {
+ return new GroupTask(
+ new Task(task1),
+ task2 != null ? new Task(task2) : null,
+ mSplitBounds);
+ }
}
diff --git a/quickstep/src/com/android/quickstep/views/DesktopTaskView.java b/quickstep/src/com/android/quickstep/views/DesktopTaskView.java
new file mode 100644
index 0000000000..9874f9644a
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/views/DesktopTaskView.java
@@ -0,0 +1,467 @@
+/*
+ * 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.util.SplitConfigurationOptions.STAGE_POSITION_UNDEFINED;
+
+import android.content.Context;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.graphics.drawable.ShapeDrawable;
+import android.graphics.drawable.shapes.RoundRectShape;
+import android.os.SystemProperties;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.MotionEvent;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.touch.PagedOrientationHandler;
+import com.android.launcher3.util.RunnableList;
+import com.android.quickstep.RecentsModel;
+import com.android.quickstep.SystemUiProxy;
+import com.android.quickstep.TaskThumbnailCache;
+import com.android.quickstep.util.CancellableTask;
+import com.android.quickstep.util.RecentsOrientedState;
+import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.recents.model.ThumbnailData;
+
+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 {
+
+ /** Flag to indicate whether desktop mode is available on the device */
+ public static final boolean DESKTOP_MODE_SUPPORTED = SystemProperties.getBoolean(
+ "persist.wm.debug.desktop_mode", false);
+
+ private static final String TAG = DesktopTaskView.class.getSimpleName();
+
+ private static final boolean DEBUG = true;
+
+ private List mTasks;
+
+ private final ArrayList mSnapshotViews = new ArrayList<>();
+
+ /** Maps {@code taskIds} to corresponding {@link TaskThumbnailView}s */
+ private final SparseArray mSnapshotViewMap = new SparseArray<>();
+
+ private final ArrayList> mPendingThumbnailRequests = new ArrayList<>();
+
+ 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);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ 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));
+ // TODO(b/244348395): this should be wallpaper
+ setBackground(background);
+
+ mSnapshotViews.add(mSnapshotView);
+ }
+
+ @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 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());
+ }
+ if (tasks.isEmpty()) {
+ return;
+ }
+ cancelPendingLoadTasks();
+
+ mTasks = tasks;
+ mSnapshotViewMap.clear();
+
+ // Ensure there are equal number of snapshot views and tasks.
+ // More tasks than views, add views. More views than tasks, remove views.
+ // TODO(b/251586230): use a ViewPool for creating TaskThumbnailViews
+ if (mSnapshotViews.size() > mTasks.size()) {
+ int diff = mSnapshotViews.size() - mTasks.size();
+ for (int i = 0; i < diff; i++) {
+ TaskThumbnailView snapshotView = mSnapshotViews.remove(0);
+ removeView(snapshotView);
+ }
+ } else if (mSnapshotViews.size() < mTasks.size()) {
+ int diff = mTasks.size() - mSnapshotViews.size();
+ for (int i = 0; i < diff; i++) {
+ TaskThumbnailView snapshotView = new TaskThumbnailView(getContext());
+ mSnapshotViews.add(snapshotView);
+ addView(snapshotView, new LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
+ }
+ }
+
+ for (int i = 0; i < mTasks.size(); i++) {
+ Task task = mTasks.get(i);
+ TaskThumbnailView snapshotView = mSnapshotViews.get(i);
+ snapshotView.bind(task);
+ mSnapshotViewMap.put(task.key.id, snapshotView);
+ }
+
+ updateTaskIdContainer();
+ updateTaskIdAttributeContainer();
+
+ setOrientationState(orientedState);
+ }
+
+ private void updateTaskIdContainer() {
+ // TODO(b/249371338): TaskView expects the array to have at least 2 elements.
+ // At least 2 elements in the array
+ mTaskIdContainer = new int[Math.max(mTasks.size(), 2)];
+ for (int i = 0; i < mTasks.size(); i++) {
+ mTaskIdContainer[i] = mTasks.get(i).key.id;
+ }
+ }
+
+ private void updateTaskIdAttributeContainer() {
+ // TODO(b/249371338): TaskView expects the array to have at least 2 elements.
+ // At least 2 elements in the array
+ mTaskIdAttributeContainer = new TaskIdAttributeContainer[Math.max(mTasks.size(), 2)];
+ for (int i = 0; i < mTasks.size(); i++) {
+ Task task = mTasks.get(i);
+ TaskThumbnailView thumbnailView = mSnapshotViewMap.get(task.key.id);
+ mTaskIdAttributeContainer[i] = createAttributeContainer(task, thumbnailView);
+ }
+ }
+
+ private TaskIdAttributeContainer createAttributeContainer(Task task,
+ TaskThumbnailView thumbnailView) {
+ return new TaskIdAttributeContainer(task, thumbnailView, null, STAGE_POSITION_UNDEFINED);
+ }
+
+ @Nullable
+ @Override
+ public Task getTask() {
+ // TODO(b/249371338): returning first task. This won't work well with multiple tasks.
+ return mTasks.size() > 0 ? mTasks.get(0) : null;
+ }
+
+ @Override
+ public TaskThumbnailView getThumbnail() {
+ // TODO(b/249371338): returning single thumbnail. This won't work well with multiple tasks.
+ Task task = getTask();
+ if (task != null) {
+ return mSnapshotViewMap.get(task.key.id);
+ }
+ return null;
+ }
+
+ @Override
+ public boolean containsTaskId(int taskId) {
+ // Thumbnail map contains taskId -> thumbnail map. Use the keys for contains
+ return mSnapshotViewMap.contains(taskId);
+ }
+
+ @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 (Task task : mTasks) {
+ CancellableTask> thumbLoadRequest =
+ thumbnailCache.updateThumbnailInBackground(task, thumbnailData -> {
+ TaskThumbnailView thumbnailView = mSnapshotViewMap.get(task.key.id);
+ if (thumbnailView != null) {
+ thumbnailView.setThumbnail(task, thumbnailData);
+ }
+ });
+ if (thumbLoadRequest != null) {
+ mPendingThumbnailRequests.add(thumbLoadRequest);
+ }
+ }
+ }
+ } else {
+ if (needsUpdate(changes, FLAG_UPDATE_THUMBNAIL)) {
+ for (Task task : mTasks) {
+ TaskThumbnailView thumbnailView = mSnapshotViewMap.get(task.key.id);
+ if (thumbnailView != null) {
+ thumbnailView.setThumbnail(null, null);
+ }
+ // Reset the task thumbnail ref
+ task.thumbnail = null;
+ }
+ }
+ }
+ }
+
+ @Override
+ public void setOrientationState(RecentsOrientedState orientationState) {
+ // TODO(b/249371338): this copies logic from TaskView
+ PagedOrientationHandler orientationHandler = orientationState.getOrientationHandler();
+ boolean isRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
+ DeviceProfile deviceProfile = mActivity.getDeviceProfile();
+
+ LayoutParams iconParams = (LayoutParams) mIconView.getLayoutParams();
+
+ int thumbnailTopMargin = deviceProfile.overviewTaskThumbnailTopMarginPx;
+ int taskIconHeight = deviceProfile.overviewTaskIconSizePx;
+ int taskMargin = deviceProfile.overviewTaskMarginPx;
+
+ orientationHandler.setTaskIconParams(iconParams, taskMargin, taskIconHeight,
+ thumbnailTopMargin, isRtl);
+
+ LayoutParams snapshotParams = (LayoutParams) mSnapshotView.getLayoutParams();
+ snapshotParams.topMargin = thumbnailTopMargin;
+
+ for (int i = 0; i < mSnapshotViewMap.size(); i++) {
+ TaskThumbnailView thumbnailView = mSnapshotViewMap.valueAt(i);
+ thumbnailView.setLayoutParams(snapshotParams);
+ }
+ }
+
+ @Override
+ protected void cancelPendingLoadTasks() {
+ for (CancellableTask> cancellableTask : mPendingThumbnailRequests) {
+ cancellableTask.cancel();
+ }
+ mPendingThumbnailRequests.clear();
+ }
+
+ @Override
+ public boolean offerTouchToChildren(MotionEvent event) {
+ return false;
+ }
+
+ @Override
+ protected boolean showTaskMenuWithContainer(IconView iconView) {
+ return false;
+ }
+
+ @Nullable
+ @Override
+ public RunnableList launchTaskAnimated() {
+ RunnableList endCallback = new RunnableList();
+ SystemUiProxy.INSTANCE.get(getContext()).showDesktopApps();
+ RecentsView, ?> recentsView = getRecentsView();
+ recentsView.addSideTaskLaunchCallback(endCallback);
+ return endCallback;
+ }
+
+ @Override
+ public void launchTask(@NonNull Consumer callback, boolean freezeTaskList) {
+ SystemUiProxy.INSTANCE.get(getContext()).showDesktopApps();
+ callback.accept(true);
+ }
+
+ @Override
+ void refreshThumbnails(@Nullable HashMap thumbnailDatas) {
+ // Sets new thumbnails based on the incoming data and refreshes the rest.
+ // Create a copy of the thumbnail map, so we can track thumbnails that need refreshing.
+ SparseArray thumbnailsToRefresh = mSnapshotViewMap.clone();
+ if (thumbnailDatas != null) {
+ for (Task task : mTasks) {
+ int key = task.key.id;
+ TaskThumbnailView thumbnailView = thumbnailsToRefresh.get(key);
+ ThumbnailData thumbnailData = thumbnailDatas.get(key);
+ if (thumbnailView != null && thumbnailData != null) {
+ thumbnailView.setThumbnail(task, thumbnailData);
+ // Remove this thumbnail from the list that should be refreshed.
+ thumbnailsToRefresh.remove(key);
+ }
+ }
+ }
+
+ // Refresh the rest that were not updated.
+ for (int i = 0; i < thumbnailsToRefresh.size(); i++) {
+ thumbnailsToRefresh.valueAt(i).refresh();
+ }
+ }
+
+ @Override
+ public TaskThumbnailView[] getThumbnails() {
+ TaskThumbnailView[] thumbnails = new TaskThumbnailView[mSnapshotViewMap.size()];
+ for (int i = 0; i < thumbnails.length; i++) {
+ thumbnails[i] = mSnapshotViewMap.valueAt(i);
+ }
+ return thumbnails;
+ }
+
+ @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 (Task task : mTasks) {
+ TaskThumbnailView thumbnailView = mSnapshotViewMap.get(task.key.id);
+ if (thumbnailView != null) {
+ thumbnailView.setThumbnail(task, null);
+ }
+ }
+ setOverlayEnabled(false);
+ onTaskListVisibilityChanged(false);
+ }
+
+ @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 thumbnails = mSnapshotViewMap.size();
+ if (thumbnails == 0) {
+ return;
+ }
+
+ int windowWidth = mActivity.getDeviceProfile().widthPx;
+ int windowHeight = mActivity.getDeviceProfile().heightPx;
+
+ 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 (int i = 0; i < mTasks.size(); i++) {
+ Task task = mTasks.get(i);
+ 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);
+
+ TaskThumbnailView thumbnailView = mSnapshotViewMap.get(task.key.id);
+ 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);
+ 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) {
+ // Intentional no-op to prevent setting smart actions overlay on thumbnails
+ }
+
+ @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;
+ for (int i = 0; i < mSnapshotViewMap.size(); i++) {
+ TaskThumbnailView thumbnailView = mSnapshotViewMap.valueAt(i);
+ thumbnailView.getTaskOverlay().setFullscreenProgress(progress);
+ updateSnapshotRadius();
+ }
+ }
+
+ @Override
+ protected void updateSnapshotRadius() {
+ for (int i = 0; i < mSnapshotViewMap.size(); i++) {
+ mSnapshotViewMap.valueAt(i).setFullscreenParams(mCurrentFullscreenParams);
+ }
+ }
+
+ @Override
+ protected void setIconAndDimTransitionProgress(float progress, boolean invert) {
+ // no-op
+ }
+
+ @Override
+ public void setColorTint(float amount, int tintColor) {
+ for (int i = 0; i < mSnapshotViewMap.size(); i++) {
+ mSnapshotViewMap.valueAt(i).setDimAlpha(amount);
+ }
+ }
+
+ @Override
+ protected void applyThumbnailSplashAlpha() {
+ for (int i = 0; i < mSnapshotViewMap.size(); i++) {
+ mSnapshotViewMap.valueAt(i).setSplashAlpha(mTaskThumbnailSplashAlpha);
+ }
+ }
+
+ @Override
+ void setThumbnailVisibility(int visibility) {
+ for (int i = 0; i < mSnapshotViewMap.size(); i++) {
+ mSnapshotViewMap.valueAt(i).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/GroupedTaskView.java b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
index 3a5f606467..71b0c60971 100644
--- a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
+++ b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
@@ -224,6 +224,12 @@ public class GroupedTaskView extends TaskView {
mSnapshotView2.refresh();
}
+ @Override
+ public boolean containsTaskId(int taskId) {
+ return (mTask != null && mTask.key.id == taskId)
+ || (mSecondaryTask != null && mSecondaryTask.key.id == taskId);
+ }
+
@Override
public TaskThumbnailView[] getThumbnails() {
return new TaskThumbnailView[]{mSnapshotView, mSnapshotView2};
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 0e0acf0173..eeaf23821d 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -54,6 +54,7 @@ import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITIO
import static com.android.launcher3.util.SystemUiController.UI_STATE_FULLSCREEN_TASK;
import static com.android.quickstep.TaskUtils.checkCurrentOrManagedUserId;
import static com.android.quickstep.views.ClearAllButton.DISMISS_ALPHA;
+import static com.android.quickstep.views.DesktopTaskView.DESKTOP_MODE_SUPPORTED;
import static com.android.quickstep.views.OverviewActionsView.FLAG_IS_NOT_TABLET;
import static com.android.quickstep.views.OverviewActionsView.FLAG_SINGLE_TASK;
import static com.android.quickstep.views.OverviewActionsView.HIDDEN_NON_ZERO_ROTATION;
@@ -72,6 +73,7 @@ import android.animation.PropertyValuesHolder;
import android.animation.ValueAnimator;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
+import android.app.WindowConfiguration;
import android.content.Context;
import android.content.LocusId;
import android.content.res.Configuration;
@@ -171,6 +173,7 @@ import com.android.quickstep.ViewUtils;
import com.android.quickstep.util.ActiveGestureErrorDetector;
import com.android.quickstep.util.ActiveGestureLog;
import com.android.quickstep.util.AnimUtils;
+import com.android.quickstep.util.DesktopTask;
import com.android.quickstep.util.GroupTask;
import com.android.quickstep.util.LayoutUtils;
import com.android.quickstep.util.RecentsOrientedState;
@@ -195,6 +198,7 @@ import com.android.systemui.shared.system.TaskStackChangeListeners;
import com.android.wm.shell.pip.IPipAnimationListener;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Objects;
@@ -477,10 +481,11 @@ public abstract class RecentsView mTaskViewPool;
private final ViewPool mGroupedTaskViewPool;
+ private final ViewPool mDesktopTaskViewPool;
private final TaskOverlayFactory mTaskOverlayFactory;
@@ -737,6 +742,8 @@ public abstract class RecentsView(context, this,
R.layout.task_grouped, 20 /* max size */, 10 /* initial size */);
+ mDesktopTaskViewPool = new ViewPool<>(context, this, R.layout.task_desktop,
+ 5 /* max size */, 1 /* initial size */);
mIsRtl = mOrientationHandler.getRecentsRtlSetting(getResources());
setLayoutDirection(mIsRtl ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR);
@@ -981,6 +988,8 @@ public abstract class RecentsView= 0; i--) {
GroupTask groupTask = taskGroups.get(i);
- boolean hasMultipleTasks = groupTask.hasMultipleTasks();
- TaskView taskView = getTaskViewFromPool(hasMultipleTasks);
+ TaskView taskView = getTaskViewFromPool(groupTask.taskViewType);
addView(taskView);
- if (hasMultipleTasks) {
+ if (taskView instanceof GroupedTaskView) {
boolean firstTaskIsLeftTopTask =
groupTask.mSplitBounds.leftTopTaskId == groupTask.task1.key.id;
Task leftTopTask = firstTaskIsLeftTopTask ? groupTask.task1 : groupTask.task2;
Task rightBottomTask = firstTaskIsLeftTopTask ? groupTask.task2 : groupTask.task1;
((GroupedTaskView) taskView).bind(leftTopTask, rightBottomTask, mOrientationState,
groupTask.mSplitBounds);
+ } else if (taskView instanceof DesktopTaskView) {
+ ((DesktopTaskView) taskView).bind(((DesktopTask) groupTask).tasks,
+ mOrientationState);
} else {
taskView.bind(groupTask.task1, mOrientationState);
}
}
+
if (!taskGroups.isEmpty()) {
addView(mClearAllButton);
}
@@ -2123,10 +2134,19 @@ public abstract class RecentsView T getTaskViewFromPool(boolean isGrouped) {
- T taskView = isGrouped ?
- (T) mGroupedTaskViewPool.getView() :
- (T) mTaskViewPool.getView();
+ private TaskView getTaskViewFromPool(@TaskView.Type int type) {
+ TaskView taskView;
+ switch (type) {
+ case TaskView.Type.GROUPED:
+ taskView = mGroupedTaskViewPool.getView();
+ break;
+ case TaskView.Type.DESKTOP:
+ taskView = mDesktopTaskViewPool.getView();
+ break;
+ case TaskView.Type.SINGLE:
+ default:
+ taskView = mTaskViewPool.getView();
+ }
taskView.setTaskViewId(mTaskViewIdCount);
if (mTaskViewIdCount == Integer.MAX_VALUE) {
mTaskViewIdCount = 0;
@@ -2318,12 +2338,19 @@ public abstract class RecentsView 1;
+ boolean needDesktopTask = hasDesktopTask(runningTasks);
if (shouldAddStubTaskView(runningTasks)) {
boolean wasEmpty = getChildCount() == 0;
// Add an empty view for now until the task plan is loaded and applied
final TaskView taskView;
- if (needGroupTaskView) {
- taskView = getTaskViewFromPool(true);
+ if (needDesktopTask) {
+ taskView = getTaskViewFromPool(TaskView.Type.DESKTOP);
+ mTmpRunningTasks = Arrays.copyOf(runningTasks, runningTasks.length);
+ addView(taskView, 0);
+ ((DesktopTaskView) taskView).bind(Arrays.asList(mTmpRunningTasks),
+ mOrientationState);
+ } else if (needGroupTaskView) {
+ taskView = getTaskViewFromPool(TaskView.Type.GROUPED);
mTmpRunningTasks = new Task[]{runningTasks[0], runningTasks[1]};
addView(taskView, 0);
// When we create a placeholder task view mSplitBoundsConfig will be null, but with
@@ -2332,7 +2359,7 @@ public abstract class RecentsView LauncherAtom.Task.newBuilder()
+ .setComponentName(component.flattenToShortString())
+ .setIndex(screenId))
+ .orElse(LauncherAtom.Task.newBuilder()));
break;
default:
break;