From 15e2c7734bfa74ebece68c9702748200d9eece4c Mon Sep 17 00:00:00 2001 From: Alex Chau Date: Wed, 10 Apr 2024 17:45:40 +0100 Subject: [PATCH] Refactor TaskView.getTaskIds and its attribute container to support any number of tasks - getTaskIds and getTaskIdAttributeContainers will now return an array with any sizes: - Not bound -> empty array - Single task -> 1 element array - Split task -> 2 elements array - Desktop task -> N elements array - Updates TaskView.containsTaskId/containsMultipleTasks accordingly - Removed unnecessary overrides in GroupedTaskView and DesktopTaskView - Updates RecentsView.hasAllValidTaskIds accordingly - Reviewed all direct and indirect usage of getTaskIds, to make sure caller don't access the array without length checking; migrate usage to stream if applicable - Simplfied RecentsView.shouldAddStubTaskView to use getTaskViewByTaskIds to find TaksView with exact match of taskIds - Simplfied RecentsView.setContentAlpha to match TaskView ref instead of doing taskId matching Bug: 249371338 Test: With single, split, desktop task in Overview, try different CUJs like launch, menu, dismiss, scroll, swipe up etc. Flag: NONE Change-Id: I39a5a36c85ce8b366fe25132a349b08244fb1217 --- .../fallback/FallbackRecentsView.java | 13 ++- .../quickstep/views/DesktopTaskView.java | 14 +-- .../quickstep/views/GroupedTaskView.java | 47 +++++--- .../android/quickstep/views/RecentsView.java | 88 ++++++++------- .../com/android/quickstep/views/TaskView.java | 100 ++++++++---------- 5 files changed, 127 insertions(+), 135 deletions(-) diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java index d881a1f735..b79586bae8 100644 --- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java +++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java @@ -53,6 +53,7 @@ import com.android.quickstep.views.TaskView; import com.android.systemui.shared.recents.model.Task; import java.util.ArrayList; +import java.util.Arrays; public class FallbackRecentsView extends RecentsView implements StateListener { @@ -144,8 +145,9 @@ public class FallbackRecentsView extends RecentsView taskId == mHomeTask.key.id)) { mHomeTask = null; setRunningTaskHidden(false); } @@ -182,13 +184,14 @@ public class FallbackRecentsView extends RecentsView taskId == mHomeTask.key.id) && !taskGroups.isEmpty()) { // Check if the task list has running task boolean found = false; for (GroupTask group : taskGroups) { - if (group.containsTask(runningTaskId)) { + if (Arrays.stream(runningTaskIds).allMatch(group::containsTask)) { found = true; break; } diff --git a/quickstep/src/com/android/quickstep/views/DesktopTaskView.java b/quickstep/src/com/android/quickstep/views/DesktopTaskView.java index a0ec525167..8acf54dd6c 100644 --- a/quickstep/src/com/android/quickstep/views/DesktopTaskView.java +++ b/quickstep/src/com/android/quickstep/views/DesktopTaskView.java @@ -204,18 +204,14 @@ public class DesktopTaskView extends TaskView { } 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)]; + mTaskIdContainer = new int[mTasks.size()]; 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)]; + mTaskIdAttributeContainer = new TaskIdAttributeContainer[mTasks.size()]; for (int i = 0; i < mTasks.size(); i++) { Task task = mTasks.get(i); TaskThumbnailViewDeprecated thumbnailView = mSnapshotViewMap.get(task.key.id); @@ -247,12 +243,6 @@ public class DesktopTaskView extends TaskView { return mTaskThumbnailViewDeprecated; } - @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(); diff --git a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java index a5937129aa..153965449d 100644 --- a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java +++ b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java @@ -6,6 +6,7 @@ import static com.android.launcher3.Flags.enableOverviewIconMenu; import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT; import static com.android.quickstep.util.SplitScreenUtils.convertLauncherSplitBoundsToShell; +import android.app.ActivityTaskManager; import android.content.Context; import android.graphics.Point; import android.graphics.PointF; @@ -46,6 +47,7 @@ import kotlin.Unit; import java.util.Arrays; import java.util.HashMap; +import java.util.Optional; import java.util.function.Consumer; /** @@ -129,9 +131,11 @@ public class GroupedTaskView extends TaskView { @Nullable SplitBounds splitBoundsConfig) { super.bind(primary, orientedState); mSecondaryTask = secondary; - mTaskIdContainer[1] = secondary.key.id; - mTaskIdAttributeContainer[1] = new TaskIdAttributeContainer(secondary, mSnapshotView2, - mIconView2, STAGE_POSITION_BOTTOM_OR_RIGHT); + mTaskIdContainer = new int[]{mTaskIdContainer[0], secondary.key.id}; + mTaskIdAttributeContainer = new TaskIdAttributeContainer[]{ + mTaskIdAttributeContainer[0], + new TaskIdAttributeContainer(secondary, mSnapshotView2, + mIconView2, STAGE_POSITION_BOTTOM_OR_RIGHT)}; mTaskIdAttributeContainer[0].setStagePosition( SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT); mSnapshotView2.bind(secondary); @@ -154,6 +158,9 @@ public class GroupedTaskView extends TaskView { public void setUpShowAllInstancesListener() { // sets up the listener for the left/top task super.setUpShowAllInstancesListener(); + if (mTaskIdAttributeContainer.length < 2) { + return; + } // right/bottom task's base package name String taskPackageName = mTaskIdAttributeContainer[1].getTask().key.getPackageName(); @@ -307,17 +314,21 @@ 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 TaskThumbnailViewDeprecated[] getThumbnails() { return new TaskThumbnailViewDeprecated[]{mTaskThumbnailViewDeprecated, mSnapshotView2}; } + /** + * Returns taskId that split selection was initiated with, + * {@link ActivityTaskManager#INVALID_TASK_ID} if no tasks in this TaskView are part of + * split selection + */ + protected int getThisTaskCurrentlyInSplitSelection() { + int initialTaskId = getRecentsView().getSplitSelectController().getInitialTaskId(); + return containsTaskId(initialTaskId) ? initialTaskId : INVALID_TASK_ID; + } + @Override protected int getLastSelectedChildTaskIndex() { SplitSelectStateController splitSelectController = @@ -382,13 +393,15 @@ public class GroupedTaskView extends TaskView { } else { // Currently being split with this taskView, let the non-split selected thumbnail // take up full thumbnail area - TaskIdAttributeContainer container = - mTaskIdAttributeContainer[initSplitTaskId == mTask.key.id ? 1 : 0]; - container.getThumbnailView().measure(widthMeasureSpec, - View.MeasureSpec.makeMeasureSpec( - heightSize - - mContainer.getDeviceProfile().overviewTaskThumbnailTopMarginPx, - MeasureSpec.EXACTLY)); + Optional nonSplitContainer = Arrays.stream( + mTaskIdAttributeContainer).filter( + container -> container.getTask().key.id != initSplitTaskId).findAny(); + nonSplitContainer.ifPresent( + taskIdAttributeContainer -> taskIdAttributeContainer.getThumbnailView().measure( + widthMeasureSpec, MeasureSpec.makeMeasureSpec( + heightSize - mContainer.getDeviceProfile() + .overviewTaskThumbnailTopMarginPx, + MeasureSpec.EXACTLY))); } if (!enableOverviewIconMenu()) { updateIconPlacement(); @@ -529,7 +542,7 @@ public class GroupedTaskView extends TaskView { mDigitalWellBeingToast.setBannerVisibility(visibility); mSnapshotView2.setVisibility(visibility); mDigitalWellBeingToast2.setBannerVisibility(visibility); - } else if (taskId == getTaskIds()[0]) { + } else if (mTaskIdContainer.length > 0 && mTaskIdContainer[0] == taskId) { mTaskThumbnailViewDeprecated.setVisibility(visibility); mDigitalWellBeingToast.setBannerVisibility(visibility); } else { diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java index 5daafcf23c..d01c554a5d 100644 --- a/quickstep/src/com/android/quickstep/views/RecentsView.java +++ b/quickstep/src/com/android/quickstep/views/RecentsView.java @@ -626,7 +626,6 @@ public abstract class RecentsView 0 + && Arrays.stream(taskIds).noneMatch(taskId -> taskId == INVALID_TASK_ID); } public void setOverviewStateEnabled(boolean enabled) { @@ -1720,10 +1721,12 @@ public abstract class RecentsView 0) { newFocusedTaskView = getTaskViewAt(0); @@ -1830,10 +1833,10 @@ public abstract class RecentsView 0) { targetPage = indexOfChild(requireTaskViewAt(0)); @@ -1963,7 +1966,8 @@ public abstract class RecentsView= 0; i--) { TaskView taskView = requireTaskViewAt(i); - if (mIgnoreResetTaskId != taskView.getTaskIds()[0]) { + if (Arrays.stream(taskView.getTaskIds()).noneMatch( + taskId -> taskId == mIgnoreResetTaskId)) { taskView.resetViewTransforms(); taskView.setIconScaleAndDim(mTaskIconScaledDown ? 0 : 1); taskView.setStableAlpha(mContentAlpha); @@ -2343,7 +2347,7 @@ public abstract class RecentsView 1) { - // Ensure all taskIds matches the TaskView, otherwise add a stub task. - return Arrays.stream(runningTasks).anyMatch( - runningTask -> !taskView.containsTaskId(runningTask.key.id)); + int[] runningTaskIds = Arrays.stream(runningTasks).mapToInt(task -> task.key.id).toArray(); + TaskView matchingTaskView = null; + if (hasDesktopTask(runningTasks) && runningTaskIds.length == 1) { + // TODO(b/249371338): Unsure if it's expected, desktop runningTasks only have a single + // taskId, therefore we match any DesktopTaskView that contains the runningTaskId. + TaskView taskview = getTaskViewByTaskId(runningTaskIds[0]); + if (taskview instanceof DesktopTaskView) { + matchingTaskView = taskview; + } } else { - // Ensure the TaskView only contains a single taskId, or is a DesktopTask, - // otherwise add a stub task. - // TODO(b/249371338): Figure out why DesktopTask only have a single runningTask. - return taskView.containsMultipleTasks() && !taskView.isDesktopTask(); + matchingTaskView = getTaskViewByTaskIds(runningTaskIds); } + return matchingTaskView == null; } /** @@ -4273,13 +4274,10 @@ public abstract class RecentsView= 0; i--) { TaskView child = requireTaskViewAt(i); - int[] childTaskIds = child.getTaskIds(); - if (runningTaskId != INVALID_TASK_ID - && mRunningTaskTileHidden - && (childTaskIds[0] == runningTaskId || childTaskIds[1] == runningTaskId)) { + if (runningTaskView != null && mRunningTaskTileHidden && child == runningTaskView) { continue; } child.setStableAlpha(alpha); @@ -4753,7 +4751,7 @@ public abstract class RecentsView { if (isSuccess) { - if (tv.getTaskIds()[1] != -1 && mRemoteTargetHandles != null) { + if (tv instanceof GroupedTaskView && hasAllValidTaskIds(tv.getTaskIds()) + && mRemoteTargetHandles != null) { // TODO(b/194414938): make this part of the animations instead. TaskViewUtils.createSplitAuxiliarySurfacesAnimator( mRemoteTargetHandles[0].getTransformParams().getTargetSet().nonApps, @@ -5879,8 +5878,7 @@ public abstract class RecentsView myTaskId == 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 + * Returns a copy of integer array containing taskIds of all tasks in the TaskView. */ public int[] getTaskIds() { return Arrays.copyOf(mTaskIdContainer, mTaskIdContainer.length); } public boolean containsMultipleTasks() { - return mTaskIdContainer[1] != -1; + return mTaskIdContainer.length > 1; } /** @@ -833,25 +834,6 @@ public class TaskView extends FrameLayout implements Reusable { return super.dispatchTouchEvent(ev); } - /** - * @return taskId that split selection was initiated with, - * {@link ActivityTaskManager#INVALID_TASK_ID} if no tasks in this TaskView are part of - * split selection - */ - protected int getThisTaskCurrentlyInSplitSelection() { - SplitSelectStateController splitSelectController = - getRecentsView().getSplitSelectController(); - int initSplitTaskId = INVALID_TASK_ID; - for (TaskIdAttributeContainer container : getTaskIdAttributeContainers()) { - int taskId = container.getTask().key.id; - if (taskId == splitSelectController.getInitialTaskId()) { - initSplitTaskId = taskId; - break; - } - } - return initSplitTaskId; - } - private void onClick(View view) { if (getTask() == null) { Log.d("b/310064698", "onClick - task is null"); @@ -872,10 +854,13 @@ 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 + * second app. {@code false} otherwise */ protected boolean confirmSecondSplitSelectApp() { int index = getLastSelectedChildTaskIndex(); + if (index >= mTaskIdAttributeContainer.length) { + return false; + } TaskIdAttributeContainer container = mTaskIdAttributeContainer[index]; if (container != null) { return getRecentsView().confirmSplitSelect(this, container.getTask(), @@ -897,6 +882,7 @@ public class TaskView extends FrameLayout implements Reusable { /** * Starts the task associated with this view and animates the startup. + * * @return CompletionStage to indicate the animation completion or null if the launch failed. */ @Nullable @@ -904,7 +890,7 @@ public class TaskView extends FrameLayout implements Reusable { if (mTask != null) { TestLogging.recordEvent( TestProtocol.SEQUENCE_MAIN, "startActivityFromRecentsAsync", mTask); - ActivityOptionsWrapper opts = mContainer.getActivityLaunchOptions(this, null); + ActivityOptionsWrapper opts = mContainer.getActivityLaunchOptions(this, null); opts.options.setLaunchDisplayId( getDisplay() == null ? DEFAULT_DISPLAY : getDisplay().getDisplayId()); if (ActivityManagerWrapper.getInstance() @@ -1092,6 +1078,7 @@ public class TaskView extends FrameLayout implements Reusable { /** * See {@link TaskDataChanges} + * * @param visible If this task view will be visible to the user in overview or hidden */ public void onTaskListVisibilityChanged(boolean visible) { @@ -1100,6 +1087,7 @@ public class TaskView extends FrameLayout implements Reusable { /** * See {@link TaskDataChanges} + * * @param visible If this task view will be visible to the user in overview or hidden */ public void onTaskListVisibilityChanged(boolean visible, @TaskDataChanges int changes) { @@ -1188,29 +1176,34 @@ public class TaskView extends FrameLayout implements Reusable { } protected boolean showTaskMenuWithContainer(TaskViewIcon iconView) { - TaskIdAttributeContainer menuContainer = - mTaskIdAttributeContainer[iconView == mIconView ? 0 : 1]; + Optional menuContainer = Arrays.stream( + mTaskIdAttributeContainer).filter( + container -> container.getIconView() == iconView).findAny(); + if (menuContainer.isEmpty()) { + return false; + } DeviceProfile dp = mContainer.getDeviceProfile(); if (enableOverviewIconMenu() && iconView instanceof IconAppChipView) { ((IconAppChipView) iconView).revealAnim(/* isRevealing= */ true); - return TaskMenuView.showForTask(menuContainer, + return TaskMenuView.showForTask(menuContainer.get(), () -> ((IconAppChipView) iconView).revealAnim(/* isRevealing= */ false)); } else if (dp.isTablet) { int alignedOptionIndex = 0; - if (getRecentsView().isOnGridBottomRow(menuContainer.getTaskView()) && dp.isLandscape) { + if (getRecentsView().isOnGridBottomRow(menuContainer.get().getTaskView()) + && dp.isLandscape) { if (Flags.enableGridOnlyOverview()) { // With no focused task, there is less available space below the tasks, so align // the arrow to the third option in the menu. alignedOptionIndex = 2; - } else { + } else { // Bottom row of landscape grid aligns arrow to second option to avoid clipping alignedOptionIndex = 1; } } - return TaskMenuViewWithArrow.Companion.showForTask(menuContainer, + return TaskMenuViewWithArrow.Companion.showForTask(menuContainer.get(), alignedOptionIndex); } else { - return TaskMenuView.showForTask(menuContainer); + return TaskMenuView.showForTask(menuContainer.get()); } } @@ -1664,9 +1657,6 @@ public class TaskView extends FrameLayout implements Reusable { final Context context = getContext(); for (TaskIdAttributeContainer taskContainer : mTaskIdAttributeContainer) { - if (taskContainer == null) { - continue; - } for (SystemShortcut s : TraceHelper.allowIpcs( "TV.a11yInfo", () -> getEnabledShortcuts(this, taskContainer))) { info.addAction(s.createAccessibilityAction(context)); @@ -1702,9 +1692,6 @@ public class TaskView extends FrameLayout implements Reusable { } for (TaskIdAttributeContainer taskContainer : mTaskIdAttributeContainer) { - if (taskContainer == null) { - continue; - } for (SystemShortcut s : getEnabledShortcuts(this, taskContainer)) { if (s.hasHandlerForAction(action)) { @@ -1903,13 +1890,13 @@ public class TaskView extends FrameLayout implements Reusable { private int getRootViewDisplayId() { - Display display = getRootView().getDisplay(); + Display display = getRootView().getDisplay(); return display != null ? display.getDisplayId() : DEFAULT_DISPLAY; } /** - * Sets visibility for the thumbnail and associated elements (DWB banners and action chips). - * IconView is unaffected. + * Sets visibility for the thumbnail and associated elements (DWB banners and action chips). + * IconView is unaffected. * * @param taskId is only used when setting visibility to a non-{@link View#VISIBLE} value */ @@ -1966,7 +1953,8 @@ public class TaskView extends FrameLayout implements Reusable { } @Override - public void close() { } + public void close() { + } } public class TaskIdAttributeContainer {