Let TaskView.bind take GroupTask instead of Task

1. Introduce a `groupTask` property to `TaskView` and let all the
   `bind` functions take a type of `GroupTask` as the parameter
   instead of a `Task`.
2. Then let `RecentsView.removeGroupTaskInternal` take the
   `GroupTask` as the parameter instead of a `TaskView`.
3. Caches the `groupTask` before `removeGroupTaskInternal` then
   we can avoid the corresponding `deskId` being set to invalid
   on `removeViewInLayout` in the main thread, which might happen
   before `removeGroupTaskInternal`.

Bug: 408032417
Test: m
Flag: EXEMPT refactor
Change-Id: I51c5b810eb63bc1267b90b915f90164f7ca3cba5
This commit is contained in:
minch
2025-04-07 17:12:10 +00:00
parent 8a7b7a955e
commit 42efd0c6cc
8 changed files with 69 additions and 54 deletions
@@ -64,6 +64,7 @@ import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import static com.android.quickstep.util.AnimUtils.completeRunnableListCallback;
import static com.android.quickstep.util.SplitAnimationTimings.TABLET_HOME_TO_SPLIT;
import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_HOME_KEY;
import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -1395,7 +1396,9 @@ public class QuickstepLauncher extends Launcher implements RecentsViewContainer,
SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT,
/* callback= */ success -> mSplitSelectStateController.resetState(),
/* freezeTaskList= */ false,
splitTask.getSplitBounds().snapPosition,
splitTask.getSplitBounds() == null
? SNAP_TO_2_50_50
: splitTask.getSplitBounds().snapPosition,
remoteTransition);
}
@@ -52,6 +52,7 @@ import com.android.wm.shell.Flags;
import com.android.wm.shell.recents.IRecentTasksListener;
import com.android.wm.shell.shared.GroupedTaskInfo;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.shared.split.SplitBounds;
import kotlin.collections.ArraysKt;
import kotlin.collections.CollectionsKt;
@@ -429,9 +430,10 @@ public class RecentTasksList implements WindowManagerProxy.DesktopVisibilityList
final Task.TaskKey task2Key = new Task.TaskKey(taskInfo2);
final Task task2 = Task.from(task2Key, taskInfo2,
tmpLockedUsers.get(task2Key.userId) /* isLocked */);
final SplitBounds splitBounds = rawTask.getBaseGroupedTask().getSplitBounds();
final SplitConfigurationOptions.SplitBounds launcherSplitBounds =
convertShellSplitBoundsToLauncher(
rawTask.getBaseGroupedTask().getSplitBounds());
splitBounds == null ? null : convertShellSplitBoundsToLauncher(
splitBounds);
allTasks.add(new SplitTask(task1, task2, launcherSplitBounds));
} else {
allTasks.add(new SingleTask(task1));
@@ -94,11 +94,16 @@ class SingleTask(task: Task) : GroupTask(listOf(task), task.key.displayId, TaskV
* A [Task] container that must contain exactly two tasks and split bounds to represent an app-pair
* in the recent tasks list.
*/
class SplitTask(task1: Task, task2: Task, val splitBounds: SplitConfigurationOptions.SplitBounds) :
class SplitTask(task1: Task, task2: Task, val splitBounds: SplitConfigurationOptions.SplitBounds?) :
GroupTask(listOf(task1, task2), task1.key.displayId, TaskViewType.GROUPED) {
val topLeftTask: Task
get() = if (splitBounds.leftTopTaskId == tasks[0].key.id) tasks[0] else tasks[1]
get() =
when {
splitBounds == null -> tasks[0]
splitBounds.leftTopTaskId == tasks[0].key.id -> tasks[0]
else -> tasks[1]
}
val bottomRightTask: Task
get() = if (topLeftTask == tasks[0]) tasks[1] else tasks[0]
@@ -70,11 +70,12 @@ class DesktopTaskView @JvmOverloads constructor(context: Context, attrs: Attribu
type = TaskViewType.DESKTOP,
thumbnailFullscreenParams = DesktopFullscreenDrawParams(context),
) {
private val desktopTask: DesktopTask?
get() = groupTask as? DesktopTask
val deskId
get() = desktopTask?.deskId ?: DesktopVisibilityController.INACTIVE_DESK_ID
private var desktopTask: DesktopTask? = null
private val contentViewFullscreenParams = FullscreenDrawParams(context)
private val taskThumbnailViewDeprecatedPool =
@@ -298,7 +299,7 @@ class DesktopTaskView @JvmOverloads constructor(context: Context, attrs: Attribu
orientedState: RecentsOrientedState,
taskOverlayFactory: TaskOverlayFactory,
) {
this.desktopTask = desktopTask
this.groupTask = desktopTask
// Minimized tasks are shown in Overview when exploded view is enabled.
val tasks =
if (enableDesktopExplodedView()) {
@@ -364,7 +365,6 @@ class DesktopTaskView @JvmOverloads constructor(context: Context, attrs: Attribu
override fun onRecycle() {
super.onRecycle()
desktopTask = null
explodeProgress = 0.0f
viewModel = null
visibility = VISIBLE
@@ -35,7 +35,7 @@ import com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_UNDEF
import com.android.quickstep.TaskOverlayFactory
import com.android.quickstep.util.RecentsOrientedState
import com.android.quickstep.util.SplitSelectStateController
import com.android.systemui.shared.recents.model.Task
import com.android.quickstep.util.SplitTask
import com.android.systemui.shared.system.InteractionJankMonitorWrapper
import com.android.wm.shell.Flags.enableFlexibleTwoAppSplit
import com.android.wm.shell.shared.split.SplitScreenConstants.PersistentSnapPosition
@@ -120,17 +120,16 @@ class GroupedTaskView @JvmOverloads constructor(context: Context, attrs: Attribu
}
fun bind(
primaryTask: Task,
secondaryTask: Task,
splitTask: SplitTask,
orientedState: RecentsOrientedState,
taskOverlayFactory: TaskOverlayFactory,
splitBoundsConfig: SplitConfigurationOptions.SplitBounds?,
) {
this.groupTask = splitTask
cancelPendingLoadTasks()
taskContainers =
listOf(
createTaskContainer(
primaryTask,
splitTask.topLeftTask,
R.id.snapshot,
R.id.icon,
R.id.show_windows,
@@ -139,7 +138,7 @@ class GroupedTaskView @JvmOverloads constructor(context: Context, attrs: Attribu
taskOverlayFactory,
),
createTaskContainer(
secondaryTask,
splitTask.bottomRightTask,
R.id.bottomright_snapshot,
R.id.bottomRight_icon,
R.id.show_windows_right,
@@ -148,7 +147,7 @@ class GroupedTaskView @JvmOverloads constructor(context: Context, attrs: Attribu
taskOverlayFactory,
),
)
this.splitBoundsConfig = splitBoundsConfig
this.splitBoundsConfig = splitTask.splitBounds
taskContainers.forEach { it.digitalWellBeingToast?.splitBounds = splitBoundsConfig }
onBind(orientedState)
}
@@ -2040,10 +2040,7 @@ public abstract class RecentsView<
traceEnd(Trace.TRACE_TAG_APP);
traceBegin(Trace.TRACE_TAG_APP, "RecentsView.applyLoadPlan.forLoop.bind");
if (taskView instanceof GroupedTaskView groupedTaskView) {
var splitTask = (SplitTask) groupTask;
groupedTaskView.bind(splitTask.getTopLeftTask(),
splitTask.getBottomRightTask(), mOrientationState,
mTaskOverlayFactory, splitTask.getSplitBounds());
groupedTaskView.bind((SplitTask) groupTask, mOrientationState, mTaskOverlayFactory);
} else if (taskView instanceof DesktopTaskView desktopTaskView) {
desktopTaskView.bind((DesktopTask) groupTask, mOrientationState,
mTaskOverlayFactory);
@@ -2051,10 +2048,9 @@ public abstract class RecentsView<
Task task = splitTask.getTopLeftTask().key.id == stagedTaskIdToBeRemoved
? splitTask.getBottomRightTask()
: splitTask.getTopLeftTask();
taskView.bind(task, mOrientationState, mTaskOverlayFactory);
taskView.bind(new SingleTask(task), mOrientationState, mTaskOverlayFactory);
} else {
taskView.bind(((SingleTask) groupTask).getTask(), mOrientationState,
mTaskOverlayFactory);
taskView.bind((SingleTask) groupTask, mOrientationState, mTaskOverlayFactory);
}
traceEnd(Trace.TRACE_TAG_APP);
traceBegin(Trace.TRACE_TAG_APP, "RecentsView.applyLoadPlan.forLoop.addTaskView");
@@ -3042,13 +3038,14 @@ public abstract class RecentsView<
// When we create a placeholder task view mSplitBoundsConfig will be null, but with
// the actual app running we won't need to show the thumbnail until all the tasks
// load later anyways
((GroupedTaskView) taskView).bind(Task.from(groupedTaskInfo.getTaskInfo1()),
Task.from(groupedTaskInfo.getTaskInfo2()), mOrientationState,
mTaskOverlayFactory, mSplitBoundsConfig);
((GroupedTaskView) taskView).bind(
new SplitTask(Task.from(groupedTaskInfo.getTaskInfo1()),
Task.from(groupedTaskInfo.getTaskInfo2()), mSplitBoundsConfig),
mOrientationState, mTaskOverlayFactory);
} else {
taskView = getTaskViewFromPool(TaskViewType.SINGLE);
taskView.bind(Task.from(groupedTaskInfo.getTaskInfo1()), mOrientationState,
mTaskOverlayFactory);
taskView.bind(new SingleTask(Task.from(groupedTaskInfo.getTaskInfo1())),
mOrientationState, mTaskOverlayFactory);
}
if (mAddDesktopButton != null && wasEmpty) {
addView(mAddDesktopButton);
@@ -4036,12 +4033,17 @@ public abstract class RecentsView<
if (success) {
mAnyTaskHasBeenDismissed = true;
if (shouldRemoveTask && dismissedTaskView != null) {
// Caches the [groupTask] before removing it. As the [deskId] might become
// invalid by [removeViewInLayout] on the [dismissedTaskView] below. It might
// happen before [removeGroupTaskInternal] that runs on a helper thread.
final GroupTask groupTask;
if (shouldRemoveTask && dismissedTaskView != null
&& (groupTask = dismissedTaskView.getGroupTask()) != null) {
if (dismissedTaskView.isRunningTask()) {
finishRecentsAnimation(true /* toRecents */, false /* shouldPip */,
() -> removeTaskInternal(dismissedTaskView));
() -> removeGroupTaskInternal(groupTask));
} else {
removeTaskInternal(dismissedTaskView);
removeGroupTaskInternal(groupTask);
}
mContainer.getStatsLogManager().logger()
.withItemInfo(dismissedTaskView.getItemInfo())
@@ -4383,34 +4385,31 @@ public abstract class RecentsView<
return lastVisibleTaskView;
}
private void removeTaskInternal(@NonNull TaskView dismissedTaskView) {
private void removeGroupTaskInternal(@NonNull GroupTask groupTask) {
UI_HELPER_EXECUTOR
.getHandler()
.post(
() -> {
if (dismissedTaskView instanceof DesktopTaskView desktopTaskView) {
removeDesktopTaskView(desktopTaskView);
if (groupTask instanceof DesktopTask desktopTask) {
if (areMultiDesksFlagsEnabled()) {
SystemUiProxy.INSTANCE
.get(getContext())
.removeDesk(desktopTask.getDeskId());
} else if (DesktopModeFlags
.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue()) {
SystemUiProxy.INSTANCE
.get(getContext())
.removeDefaultDeskInDisplay(
mContainer.getDisplay().getDisplayId());
}
} else {
for (int taskId : dismissedTaskView.getTaskIds()) {
ActivityManagerWrapper.getInstance().removeTask(taskId);
for (Task task : groupTask.getTasks()) {
ActivityManagerWrapper.getInstance().removeTask(task.key.id);
}
}
});
}
private void removeDesktopTaskView(DesktopTaskView desktopTaskView) {
if (areMultiDesksFlagsEnabled()) {
SystemUiProxy.INSTANCE
.get(getContext())
.removeDesk(desktopTaskView.getDeskId());
} else if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue()) {
SystemUiProxy.INSTANCE
.get(getContext())
.removeDefaultDeskInDisplay(
mContainer.getDisplay().getDisplayId());
}
}
protected void onDismissAnimationEnds() {
AccessibilityManagerCompat.sendTestProtocolEventToTest(getContext(),
DISMISS_ANIMATION_ENDS_MESSAGE);
@@ -92,7 +92,9 @@ import com.android.quickstep.util.ActiveGestureErrorDetector
import com.android.quickstep.util.ActiveGestureLog
import com.android.quickstep.util.BorderAnimator
import com.android.quickstep.util.BorderAnimator.Companion.createSimpleBorderAnimator
import com.android.quickstep.util.GroupTask
import com.android.quickstep.util.RecentsOrientedState
import com.android.quickstep.util.SingleTask
import com.android.quickstep.util.TaskCornerRadius
import com.android.quickstep.util.TaskRemovedDuringLaunchListener
import com.android.quickstep.util.isExternalDisplay
@@ -131,6 +133,8 @@ constructor(
@IntDef(FLAG_UPDATE_ALL, FLAG_UPDATE_ICON, FLAG_UPDATE_THUMBNAIL, FLAG_UPDATE_CORNER_RADIUS)
annotation class TaskDataChanges
var groupTask: GroupTask? = null
val taskIds: IntArray
/** Returns a copy of integer array containing taskIds of all tasks in the TaskView. */
get() = taskContainers.map { it.task.key.id }.toIntArray()
@@ -692,6 +696,7 @@ constructor(
override fun onRecycle() {
resetPersistentViewTransforms()
groupTask = null
// Bind ViewModel to no taskIds
viewModel?.bind()
attachAlpha = 1f
@@ -934,16 +939,17 @@ constructor(
/** Updates this task view to the given {@param task}. */
open fun bind(
task: Task,
singleTask: SingleTask,
orientedState: RecentsOrientedState,
taskOverlayFactory: TaskOverlayFactory,
) {
this.groupTask = singleTask
cancelPendingLoadTasks()
this.orientedState = orientedState // Needed for dependencies
taskContainers =
listOf(
createTaskContainer(
task,
singleTask.task,
R.id.snapshot,
R.id.icon,
R.id.show_windows,
@@ -52,6 +52,7 @@ import com.android.quickstep.orientation.LandscapePagedViewHandler
import com.android.quickstep.recents.di.RecentsDependencies
import com.android.quickstep.task.thumbnail.TaskThumbnailView
import com.android.quickstep.util.RecentsOrientedState
import com.android.quickstep.util.SingleTask
import com.android.quickstep.views.LauncherRecentsView
import com.android.quickstep.views.RecentsViewContainer
import com.android.quickstep.views.TaskContainer
@@ -165,7 +166,7 @@ class AspectRatioSystemShortcutTests {
val taskContainer = createTaskContainer(task)
setScreenSizeDp(widthDp = 1200, heightDp = 800)
taskView.bind(task, orientedState, taskOverlayFactory)
taskView.bind(SingleTask(task), orientedState, taskOverlayFactory)
assertThat(factory.getShortcuts(launcher, taskContainer)).isNull()
}
@@ -181,7 +182,7 @@ class AspectRatioSystemShortcutTests {
val taskContainer = createTaskContainer(task)
setScreenSizeDp(widthDp = 599, heightDp = 599)
taskView.bind(task, orientedState, taskOverlayFactory)
taskView.bind(SingleTask(task), orientedState, taskOverlayFactory)
assertThat(factory.getShortcuts(launcher, taskContainer)).isNull()
}
@@ -199,7 +200,7 @@ class AspectRatioSystemShortcutTests {
doReturn(taskViewItemInfo).whenever(taskContainer).itemInfo
setScreenSizeDp(widthDp = 1200, heightDp = 800)
taskView.bind(task, orientedState, taskOverlayFactory)
taskView.bind(SingleTask(task), orientedState, taskOverlayFactory)
val shortcuts = factory.getShortcuts(launcher, taskContainer)
assertThat(shortcuts).hasSize(1)