Merge "Desktop tile that is a snapshot of desktop" into tm-qpr-dev

This commit is contained in:
TreeHugger Robot
2022-10-12 21:10:16 +00:00
committed by Android (Google) Code Review
11 changed files with 718 additions and 60 deletions
+46
View File
@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
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.
-->
<com.android.quickstep.views.DesktopTaskView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipChildren="true"
android:clipToOutline="true"
android:defaultFocusHighlightEnabled="false"
android:focusable="true">
<!--
TODO(b249371338): DesktopTaskView extends from TaskView. TaskView expects TaskThumbnailView
and IconView with these ids to be present. Need to refactor RecentsView to accept child
views that do not inherint from TaskView only or create a generic TaskView that have
N number of tasks.
-->
<com.android.quickstep.views.TaskThumbnailView
android:id="@+id/snapshot"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<com.android.quickstep.views.IconView
android:id="@+id/icon"
android:layout_width="0dp"
android:layout_height="0dp"
android:focusable="false"
android:importantForAccessibility="no"
android:visibility="gone" />
</com.android.quickstep.views.DesktopTaskView>
@@ -1882,6 +1882,7 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
}
private void finishCurrentTransitionToRecents() {
// TODO(b/245569277#comment2): enable once isFreeformActive is implemented
mStateCallback.setStateOnUiThread(STATE_CURRENT_TASK_FINISHED);
if (mRecentsAnimationController != null) {
mRecentsAnimationController.detachNavigationBarFromApp(true);
@@ -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<Task> 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<GroupTask> copyOf(ArrayList<GroupTask> tasks) {
ArrayList<GroupTask> 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;
}
@@ -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]
@@ -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<Task> tasks;
public DesktopTask(ArrayList<Task> 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);
}
}
@@ -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);
}
}
@@ -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<Task> mTasks;
private final ArrayList<TaskThumbnailView> mSnapshotViews = new ArrayList<>();
/** Maps {@code taskIds} to corresponding {@link TaskThumbnailView}s */
private final SparseArray<TaskThumbnailView> mSnapshotViewMap = new SparseArray<>();
private final ArrayList<CancellableTask<?>> 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<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());
}
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<Boolean> callback, boolean freezeTaskList) {
SystemUiProxy.INSTANCE.get(getContext()).showDesktopApps();
callback.accept(true);
}
@Override
void refreshThumbnails(@Nullable HashMap<Integer, ThumbnailData> 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<TaskThumbnailView> 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;
}
}
@@ -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};
@@ -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<ACTIVITY_TYPE extends StatefulActivity<STATE_T
private final InvariantDeviceProfile mIdp;
/**
* Getting views should be done via {@link #getTaskViewFromPool(boolean)}
* Getting views should be done via {@link #getTaskViewFromPool(int)}
*/
private final ViewPool<TaskView> mTaskViewPool;
private final ViewPool<GroupedTaskView> mGroupedTaskViewPool;
private final ViewPool<DesktopTaskView> mDesktopTaskViewPool;
private final TaskOverlayFactory mTaskOverlayFactory;
@@ -737,6 +742,8 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
10 /* initial size */);
mGroupedTaskViewPool = new ViewPool<>(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<ACTIVITY_TYPE extends StatefulActivity<STATE_T
}
if (child instanceof GroupedTaskView) {
mGroupedTaskViewPool.recycle((GroupedTaskView) taskView);
} else if (child instanceof DesktopTaskView) {
mDesktopTaskViewPool.recycle((DesktopTaskView) taskView);
} else {
mTaskViewPool.recycle(taskView);
}
@@ -1199,8 +1208,7 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
for (int i = 0; i < getTaskViewCount(); i++) {
TaskView taskView = requireTaskViewAt(i);
int[] taskIds = taskView.getTaskIds();
if (taskIds[0] == taskId || taskIds[1] == taskId) {
if (taskView.containsTaskId(taskId)) {
return taskView;
}
}
@@ -1481,21 +1489,24 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
// Add views as children based on whether it's grouped or single task
for (int i = taskGroups.size() - 1; i >= 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<ACTIVITY_TYPE extends StatefulActivity<STATE_T
* Handle the edge case where Recents could increment task count very high over long
* period of device usage. Probably will never happen, but meh.
*/
private <T extends TaskView> 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<ACTIVITY_TYPE extends StatefulActivity<STATE_T
}
int runningTaskViewId = -1;
boolean needGroupTaskView = runningTasks.length > 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<ACTIVITY_TYPE extends StatefulActivity<STATE_T
((GroupedTaskView)taskView).bind(mTmpRunningTasks[0], mTmpRunningTasks[1],
mOrientationState, mSplitBoundsConfig);
} else {
taskView = getTaskViewFromPool(false);
taskView = getTaskViewFromPool(TaskView.Type.SINGLE);
addView(taskView, 0);
// The temporary running task is only used for the duration between the start of the
// gesture and the task list is loaded and applied
@@ -2367,6 +2394,18 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
reloadIfNeeded();
}
private boolean hasDesktopTask(Task[] runningTasks) {
if (!DESKTOP_MODE_SUPPORTED) {
return false;
}
for (Task task : runningTasks) {
if (task.key.windowingMode == WindowConfiguration.WINDOWING_MODE_FREEFORM) {
return true;
}
}
return false;
}
/**
* Sets the running task id, cleaning up the old running task if necessary.
*/
@@ -86,7 +86,6 @@ import com.android.launcher3.util.ViewPool.Reusable;
import com.android.quickstep.RecentsModel;
import com.android.quickstep.RemoteAnimationTargets;
import com.android.quickstep.RemoteTargetGluer.RemoteTargetHandle;
import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.TaskIconCache;
import com.android.quickstep.TaskOverlayFactory;
import com.android.quickstep.TaskThumbnailCache;
@@ -135,6 +134,17 @@ public class TaskView extends FrameLayout implements Reusable {
@IntDef({FLAG_UPDATE_ALL, FLAG_UPDATE_ICON, FLAG_UPDATE_THUMBNAIL})
public @interface TaskDataChanges {}
/**
* Type of task view
*/
@Retention(SOURCE)
@IntDef({Type.SINGLE, Type.GROUPED, Type.DESKTOP})
public @interface Type {
int SINGLE = 1;
int GROUPED = 2;
int DESKTOP = 3;
}
/** The maximum amount that a task view can be scrimmed, dimmed or tinted. */
public static final float MAX_PAGE_SCRIM_ALPHA = 0.4f;
@@ -331,7 +341,7 @@ public class TaskView extends FrameLayout implements Reusable {
protected TaskThumbnailView mSnapshotView;
protected IconView mIconView;
protected final DigitalWellBeingToast mDigitalWellBeingToast;
private float mFullscreenProgress;
protected float mFullscreenProgress;
private float mGridProgress;
protected float mTaskThumbnailSplashAlpha;
private float mNonGridScale = 1;
@@ -373,8 +383,8 @@ public class TaskView extends FrameLayout implements Reusable {
/**
* Index 0 will contain taskID of left/top task, index 1 will contain taskId of bottom/right
*/
protected final int[] mTaskIdContainer = new int[]{-1, -1};
protected final TaskIdAttributeContainer[] mTaskIdAttributeContainer =
protected int[] mTaskIdContainer = new int[]{-1, -1};
protected TaskIdAttributeContainer[] mTaskIdAttributeContainer =
new TaskIdAttributeContainer[2];
private boolean mShowScreenshot;
@@ -524,6 +534,13 @@ public class TaskView extends FrameLayout implements Reusable {
return mTask;
}
/**
* Check if given {@code taskId} is tracked in this view
*/
public boolean containsTaskId(int taskId) {
return mTask != null && mTask.key.id == taskId;
}
/**
* @return integer array of two elements to be size consistent with max number of tasks possible
* index 0 will contain the taskId, index 1 will be -1 indicating a null taskID value
@@ -564,13 +581,13 @@ public class TaskView extends FrameLayout implements Reusable {
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
RecentsView recentsView = getRecentsView();
if (recentsView == null || mTask == null) {
if (recentsView == null || getTask() == null) {
return false;
}
SplitSelectStateController splitSelectStateController =
recentsView.getSplitSelectController();
if (splitSelectStateController.isSplitSelectActive() &&
splitSelectStateController.getInitialTaskId() == mTask.key.id) {
splitSelectStateController.getInitialTaskId() == getTask().key.id) {
// Prevent taps on the this taskview if it's being animated into split select state
return false;
}
@@ -597,11 +614,14 @@ public class TaskView extends FrameLayout implements Reusable {
* @return {@code true} if user is already in split select mode and this tap was to choose the
* second app. {@code false} otherwise
*/
private boolean confirmSecondSplitSelectApp() {
protected boolean confirmSecondSplitSelectApp() {
int index = getLastSelectedChildTaskIndex();
TaskIdAttributeContainer container = mTaskIdAttributeContainer[index];
return getRecentsView().confirmSplitSelect(this, container.getTask(),
container.getIconView(), container.getThumbnailView());
if (container != null) {
return getRecentsView().confirmSplitSelect(this, container.getTask(),
container.getIconView(), container.getThumbnailView());
}
return false;
}
/**
@@ -707,11 +727,6 @@ public class TaskView extends FrameLayout implements Reusable {
RecentsView recentsView = getRecentsView();
RemoteTargetHandle[] remoteTargetHandles = recentsView.mRemoteTargetHandles;
RunnableList runnableList = new RunnableList();
if (mTask != null && mTask.desktopTile) {
// clicked on desktop
SystemUiProxy.INSTANCE.get(getContext()).showDesktopApps();
return runnableList;
}
if (isRunningTask() && remoteTargetHandles != null) {
if (!mIsClickableAsLiveTile) {
return runnableList;
@@ -350,9 +350,11 @@ public class ItemInfo {
break;
case ITEM_TYPE_TASK:
itemBuilder
.setTask(LauncherAtom.Task.newBuilder()
.setComponentName(getTargetComponent().flattenToShortString())
.setIndex(screenId));
.setTask(nullableComponent
.map(component -> LauncherAtom.Task.newBuilder()
.setComponentName(component.flattenToShortString())
.setIndex(screenId))
.orElse(LauncherAtom.Task.newBuilder()));
break;
default:
break;