diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java index 41be44d66b..88ac40f171 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java +++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java @@ -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); } diff --git a/quickstep/src/com/android/quickstep/RecentTasksList.java b/quickstep/src/com/android/quickstep/RecentTasksList.java index b481fe8ede..ea9bdb01f3 100644 --- a/quickstep/src/com/android/quickstep/RecentTasksList.java +++ b/quickstep/src/com/android/quickstep/RecentTasksList.java @@ -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)); diff --git a/quickstep/src/com/android/quickstep/util/GroupTask.kt b/quickstep/src/com/android/quickstep/util/GroupTask.kt index f906899453..2772cfc16d 100644 --- a/quickstep/src/com/android/quickstep/util/GroupTask.kt +++ b/quickstep/src/com/android/quickstep/util/GroupTask.kt @@ -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] diff --git a/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt b/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt index 6705711c8a..38c0eb3cc9 100644 --- a/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt +++ b/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt @@ -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 diff --git a/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt b/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt index 2bc2c849b2..e3e987ccf7 100644 --- a/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt +++ b/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt @@ -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) } diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java index 1476613575..4b7ec8c4df 100644 --- a/quickstep/src/com/android/quickstep/views/RecentsView.java +++ b/quickstep/src/com/android/quickstep/views/RecentsView.java @@ -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); diff --git a/quickstep/src/com/android/quickstep/views/TaskView.kt b/quickstep/src/com/android/quickstep/views/TaskView.kt index d3d1b03b0a..0edd87bfe3 100644 --- a/quickstep/src/com/android/quickstep/views/TaskView.kt +++ b/quickstep/src/com/android/quickstep/views/TaskView.kt @@ -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, diff --git a/quickstep/tests/src/com/android/quickstep/AspectRatioSystemShortcutTests.kt b/quickstep/tests/src/com/android/quickstep/AspectRatioSystemShortcutTests.kt index c8be7a3f19..1e7c116f94 100644 --- a/quickstep/tests/src/com/android/quickstep/AspectRatioSystemShortcutTests.kt +++ b/quickstep/tests/src/com/android/quickstep/AspectRatioSystemShortcutTests.kt @@ -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)