Update recent tasks split pairs from split controller
- Add pairs for the top visible children when both roots are populated,
and update when either side changes.
- Remove pairs if an activity is finished, or if the user dismisses
split manually
- Remove pairs for associated tasks if they are made visible in fullscreen
Bug: 202740477
Test: atest WMShellUnitTests
Test: adb shell dumpsys activity service SystemUIService WMShell
Change-Id: I87c632dfd1a014d64ad8e25c9e68ea6ca230744e
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index a564949..ac2e448 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -240,8 +240,12 @@
@WMSingleton
@Provides
static FullscreenTaskListener provideFullscreenTaskListener(
- SyncTransactionQueue syncQueue, Optional<FullscreenUnfoldController> controller) {
- return new FullscreenTaskListener(syncQueue, controller);
+ SyncTransactionQueue syncQueue,
+ Optional<FullscreenUnfoldController> optionalFullscreenUnfoldController,
+ Optional<RecentTasksController> recentTasksOptional
+ ) {
+ return new FullscreenTaskListener(syncQueue, optionalFullscreenUnfoldController,
+ recentTasksOptional);
}
//
@@ -490,12 +494,13 @@
DisplayImeController displayImeController,
DisplayInsetsController displayInsetsController, Transitions transitions,
TransactionPool transactionPool, IconProvider iconProvider,
+ Optional<RecentTasksController> recentTasks,
Provider<Optional<StageTaskUnfoldController>> stageTaskUnfoldControllerProvider) {
if (ActivityTaskManager.supportsSplitScreenMultiWindow(context)) {
return Optional.of(new SplitScreenController(shellTaskOrganizer, syncQueue, context,
rootTaskDisplayAreaOrganizer, mainExecutor, displayImeController,
displayInsetsController, transitions, transactionPool, iconProvider,
- stageTaskUnfoldControllerProvider));
+ recentTasks, stageTaskUnfoldControllerProvider));
} else {
return Optional.empty();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java
index 3f17f2b..6e38e42 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java
@@ -35,6 +35,7 @@
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.recents.RecentTasksController;
import com.android.wm.shell.transition.Transitions;
import java.io.PrintWriter;
@@ -47,15 +48,23 @@
private static final String TAG = "FullscreenTaskListener";
private final SyncTransactionQueue mSyncQueue;
+ private final FullscreenUnfoldController mFullscreenUnfoldController;
+ private final Optional<RecentTasksController> mRecentTasksOptional;
private final SparseArray<TaskData> mDataByTaskId = new SparseArray<>();
private final AnimatableTasksListener mAnimatableTasksListener = new AnimatableTasksListener();
- private final FullscreenUnfoldController mFullscreenUnfoldController;
public FullscreenTaskListener(SyncTransactionQueue syncQueue,
Optional<FullscreenUnfoldController> unfoldController) {
+ this(syncQueue, unfoldController, Optional.empty());
+ }
+
+ public FullscreenTaskListener(SyncTransactionQueue syncQueue,
+ Optional<FullscreenUnfoldController> unfoldController,
+ Optional<RecentTasksController> recentTasks) {
mSyncQueue = syncQueue;
mFullscreenUnfoldController = unfoldController.orElse(null);
+ mRecentTasksOptional = recentTasks;
}
@Override
@@ -79,6 +88,7 @@
});
mAnimatableTasksListener.onTaskAppeared(taskInfo);
+ updateRecentsForVisibleFullscreenTask(taskInfo);
}
@Override
@@ -86,6 +96,7 @@
if (Transitions.ENABLE_SHELL_TRANSITIONS) return;
mAnimatableTasksListener.onTaskInfoChanged(taskInfo);
+ updateRecentsForVisibleFullscreenTask(taskInfo);
final TaskData data = mDataByTaskId.get(taskInfo.taskId);
final Point positionInParent = taskInfo.positionInParent;
@@ -111,6 +122,15 @@
taskInfo.taskId);
}
+ private void updateRecentsForVisibleFullscreenTask(RunningTaskInfo taskInfo) {
+ mRecentTasksOptional.ifPresent(recentTasks -> {
+ if (taskInfo.isVisible) {
+ // Remove any persisted splits if either tasks are now made fullscreen and visible
+ recentTasks.removeSplitPair(taskInfo.taskId);
+ }
+ });
+ }
+
@Override
public void attachChildSurfaceToTask(int taskId, SurfaceControl.Builder b) {
if (!mDataByTaskId.contains(taskId)) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index 83a0e60..7457be2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -66,6 +66,7 @@
import com.android.wm.shell.common.annotations.ExternalThread;
import com.android.wm.shell.common.split.SplitLayout.SplitPosition;
import com.android.wm.shell.draganddrop.DragAndDropPolicy;
+import com.android.wm.shell.recents.RecentTasksController;
import com.android.wm.shell.transition.LegacyTransitions;
import com.android.wm.shell.transition.Transitions;
@@ -124,16 +125,31 @@
private final TransactionPool mTransactionPool;
private final SplitscreenEventLogger mLogger;
private final IconProvider mIconProvider;
+ private final Optional<RecentTasksController> mRecentTasksOptional;
private final Provider<Optional<StageTaskUnfoldController>> mUnfoldControllerProvider;
private StageCoordinator mStageCoordinator;
+ // TODO(b/205019015): Remove after we clean up downstream modules
+ public SplitScreenController(ShellTaskOrganizer shellTaskOrganizer,
+ SyncTransactionQueue syncQueue, Context context,
+ RootTaskDisplayAreaOrganizer rootTDAOrganizer,
+ ShellExecutor mainExecutor, DisplayImeController displayImeController,
+ DisplayInsetsController displayInsetsController,
+ Transitions transitions, TransactionPool transactionPool, IconProvider iconProvider,
+ Provider<Optional<StageTaskUnfoldController>> unfoldControllerProvider) {
+ this(shellTaskOrganizer, syncQueue, context, rootTDAOrganizer, mainExecutor,
+ displayImeController, displayInsetsController, transitions, transactionPool,
+ iconProvider, Optional.empty(), unfoldControllerProvider);
+ }
+
public SplitScreenController(ShellTaskOrganizer shellTaskOrganizer,
SyncTransactionQueue syncQueue, Context context,
RootTaskDisplayAreaOrganizer rootTDAOrganizer,
ShellExecutor mainExecutor, DisplayImeController displayImeController,
DisplayInsetsController displayInsetsController,
Transitions transitions, TransactionPool transactionPool, IconProvider iconProvider,
+ Optional<RecentTasksController> recentTasks,
Provider<Optional<StageTaskUnfoldController>> unfoldControllerProvider) {
mTaskOrganizer = shellTaskOrganizer;
mSyncQueue = syncQueue;
@@ -147,6 +163,7 @@
mUnfoldControllerProvider = unfoldControllerProvider;
mLogger = new SplitscreenEventLogger();
mIconProvider = iconProvider;
+ mRecentTasksOptional = recentTasks;
}
public SplitScreen asSplitScreen() {
@@ -169,7 +186,7 @@
mStageCoordinator = new StageCoordinator(mContext, DEFAULT_DISPLAY, mSyncQueue,
mRootTDAOrganizer, mTaskOrganizer, mDisplayImeController,
mDisplayInsetsController, mTransitions, mTransactionPool, mLogger,
- mIconProvider, mUnfoldControllerProvider);
+ mIconProvider, mRecentTasksOptional, mUnfoldControllerProvider);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 44af43d..3c35e6a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -17,6 +17,7 @@
package com.android.wm.shell.splitscreen;
import static android.app.ActivityOptions.KEY_LAUNCH_ROOT_TASK_TOKEN;
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
import static android.view.WindowManager.TRANSIT_OPEN;
@@ -92,6 +93,7 @@
import com.android.wm.shell.common.split.SplitLayout.SplitPosition;
import com.android.wm.shell.common.split.SplitWindowManager;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.recents.RecentTasksController;
import com.android.wm.shell.splitscreen.SplitScreenController.ExitReason;
import com.android.wm.shell.transition.Transitions;
@@ -147,6 +149,10 @@
private final DisplayInsetsController mDisplayInsetsController;
private final SplitScreenTransitions mSplitTransitions;
private final SplitscreenEventLogger mLogger;
+ private final Optional<RecentTasksController> mRecentTasks;
+ // Tracks whether we should update the recent tasks. Only allow this to happen in between enter
+ // and exit, since exit itself can trigger a number of changes that update the stages.
+ private boolean mShouldUpdateRecents;
private boolean mExitSplitScreenOnHide;
private boolean mKeyguardOccluded;
@@ -191,6 +197,7 @@
DisplayInsetsController displayInsetsController, Transitions transitions,
TransactionPool transactionPool, SplitscreenEventLogger logger,
IconProvider iconProvider,
+ Optional<RecentTasksController> recentTasks,
Provider<Optional<StageTaskUnfoldController>> unfoldControllerProvider) {
mContext = context;
mDisplayId = displayId;
@@ -198,6 +205,7 @@
mRootTDAOrganizer = rootTDAOrganizer;
mTaskOrganizer = taskOrganizer;
mLogger = logger;
+ mRecentTasks = recentTasks;
mMainUnfoldController = unfoldControllerProvider.get().orElse(null);
mSideUnfoldController = unfoldControllerProvider.get().orElse(null);
@@ -238,6 +246,7 @@
DisplayInsetsController displayInsetsController, SplitLayout splitLayout,
Transitions transitions, TransactionPool transactionPool,
SplitscreenEventLogger logger,
+ Optional<RecentTasksController> recentTasks,
Provider<Optional<StageTaskUnfoldController>> unfoldControllerProvider) {
mContext = context;
mDisplayId = displayId;
@@ -255,6 +264,7 @@
mMainUnfoldController = unfoldControllerProvider.get().orElse(null);
mSideUnfoldController = unfoldControllerProvider.get().orElse(null);
mLogger = logger;
+ mRecentTasks = recentTasks;
transitions.addHandler(this);
}
@@ -560,12 +570,23 @@
private void applyExitSplitScreen(StageTaskListener childrenToTop,
WindowContainerTransaction wct, @ExitReason int exitReason) {
+ mRecentTasks.ifPresent(recentTasks -> {
+ // Notify recents if we are exiting in a way that breaks the pair, and disable further
+ // updates to splits in the recents until we enter split again
+ if (shouldBreakPairedTaskInRecents(exitReason) && mShouldUpdateRecents) {
+ recentTasks.removeSplitPair(mMainStage.getTopVisibleChildTaskId());
+ recentTasks.removeSplitPair(mSideStage.getTopVisibleChildTaskId());
+ }
+ });
+ mShouldUpdateRecents = false;
+
mSideStage.removeAllTasks(wct, childrenToTop == mSideStage);
mMainStage.deactivate(wct, childrenToTop == mMainStage);
mTaskOrganizer.applyTransaction(wct);
mSyncQueue.runInSync(t -> t
.setWindowCrop(mMainStage.mRootLeash, null)
.setWindowCrop(mSideStage.mRootLeash, null));
+
// Hide divider and reset its position.
setDividerVisibility(false);
mSplitLayout.resetDividerPosition();
@@ -580,6 +601,23 @@
}
/**
+ * Returns whether the split pair in the recent tasks list should be broken.
+ */
+ private boolean shouldBreakPairedTaskInRecents(@ExitReason int exitReason) {
+ switch (exitReason) {
+ // One of the apps doesn't support MW
+ case EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW:
+ // User has explicitly dragged the divider to dismiss split
+ case EXIT_REASON_DRAG_DIVIDER:
+ // Either of the split apps have finished
+ case EXIT_REASON_APP_FINISHED:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ /**
* Unlike exitSplitScreen, this takes a stagetype vs an actual stage-reference and populates
* an existing WindowContainerTransaction (rather than applying immediately). This is intended
* to be used when exiting split might be bundled with other window operations.
@@ -645,12 +683,28 @@
mLogger.logSideStageAppChange(getSideStagePosition(), mSideStage.getTopChildTaskUid(),
mSplitLayout.isLandscape());
}
+ updateRecentTasksSplitPair();
for (int i = mListeners.size() - 1; i >= 0; --i) {
mListeners.get(i).onTaskStageChanged(taskId, stage, visible);
}
}
+ private void updateRecentTasksSplitPair() {
+ if (!mShouldUpdateRecents) {
+ return;
+ }
+
+ mRecentTasks.ifPresent(recentTasks -> {
+ int mainStageTopTaskId = mMainStage.getTopVisibleChildTaskId();
+ int sideStageTopTaskId = mSideStage.getTopVisibleChildTaskId();
+ if (mainStageTopTaskId != INVALID_TASK_ID && sideStageTopTaskId != INVALID_TASK_ID) {
+ // Update the pair for the top tasks
+ recentTasks.addSplitPair(mainStageTopTaskId, sideStageTopTaskId);
+ }
+ });
+ }
+
private void sendSplitVisibilityChanged() {
for (int i = mListeners.size() - 1; i >= 0; --i) {
final SplitScreen.SplitScreenListener l = mListeners.get(i);
@@ -784,12 +838,16 @@
mSyncQueue.queue(wct);
mSyncQueue.runInSync(t -> updateSurfaceBounds(mSplitLayout, t));
}
- if (!mLogger.hasStartedSession() && mMainStageListener.mHasChildren
- && mSideStageListener.mHasChildren) {
- mLogger.logEnter(mSplitLayout.getDividerPositionAsFraction(),
- getMainStagePosition(), mMainStage.getTopChildTaskUid(),
- getSideStagePosition(), mSideStage.getTopChildTaskUid(),
- mSplitLayout.isLandscape());
+ if (mMainStageListener.mHasChildren && mSideStageListener.mHasChildren) {
+ mShouldUpdateRecents = true;
+ updateRecentTasksSplitPair();
+
+ if (!mLogger.hasStartedSession()) {
+ mLogger.logEnter(mSplitLayout.getDividerPositionAsFraction(),
+ getMainStagePosition(), mMainStage.getTopChildTaskUid(),
+ getSideStagePosition(), mSideStage.getTopChildTaskUid(),
+ mSplitLayout.isLandscape());
+ }
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
index 5100c56..190006e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
@@ -16,6 +16,7 @@
package com.android.wm.shell.splitscreen;
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
@@ -113,6 +114,19 @@
}
/**
+ * Returns the top visible child task's id.
+ */
+ int getTopVisibleChildTaskId() {
+ for (int i = mChildrenTaskInfo.size() - 1; i >= 0; --i) {
+ final ActivityManager.RunningTaskInfo info = mChildrenTaskInfo.valueAt(i);
+ if (info.isVisible) {
+ return info.taskId;
+ }
+ }
+ return INVALID_TASK_ID;
+ }
+
+ /**
* Returns the top activity uid for the top child task.
*/
int getTopChildTaskUid() {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/fullscreen/FullscreenTaskListenerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/fullscreen/FullscreenTaskListenerTest.java
index d6f7e54..9cbdf1e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/fullscreen/FullscreenTaskListenerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/fullscreen/FullscreenTaskListenerTest.java
@@ -35,6 +35,7 @@
import androidx.test.filters.SmallTest;
import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.recents.RecentTasksController;
import org.junit.Before;
import org.junit.Test;
@@ -52,6 +53,8 @@
@Mock
private FullscreenUnfoldController mUnfoldController;
@Mock
+ private RecentTasksController mRecentTasksController;
+ @Mock
private SurfaceControl mSurfaceControl;
private Optional<FullscreenUnfoldController> mFullscreenUnfoldController;
@@ -62,7 +65,8 @@
public void setup() {
MockitoAnnotations.initMocks(this);
mFullscreenUnfoldController = Optional.of(mUnfoldController);
- mListener = new FullscreenTaskListener(mSyncQueue, mFullscreenUnfoldController);
+ mListener = new FullscreenTaskListener(mSyncQueue, mFullscreenUnfoldController,
+ Optional.empty());
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java
index f90af23..aab1e3a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java
@@ -36,6 +36,7 @@
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.common.split.SplitLayout;
+import com.android.wm.shell.recents.RecentTasksController;
import com.android.wm.shell.transition.Transitions;
import java.util.Optional;
@@ -72,10 +73,11 @@
DisplayInsetsController insetsController, SplitLayout splitLayout,
Transitions transitions, TransactionPool transactionPool,
SplitscreenEventLogger logger,
+ Optional<RecentTasksController> recentTasks,
Provider<Optional<StageTaskUnfoldController>> unfoldController) {
super(context, displayId, syncQueue, rootTDAOrganizer, taskOrganizer, mainStage,
sideStage, imeController, insetsController, splitLayout, transitions,
- transactionPool, logger, unfoldController);
+ transactionPool, logger, recentTasks, unfoldController);
// Prepare default TaskDisplayArea for testing.
mDisplayAreaInfo = new DisplayAreaInfo(
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
index d5dee82..1eae625 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
@@ -66,6 +66,7 @@
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.common.split.SplitLayout;
+import com.android.wm.shell.recents.RecentTasksController;
import com.android.wm.shell.transition.Transitions;
import org.junit.Before;
@@ -120,8 +121,7 @@
mStageCoordinator = new SplitTestUtils.TestStageCoordinator(mContext, DEFAULT_DISPLAY,
mSyncQueue, mRootTDAOrganizer, mTaskOrganizer, mMainStage, mSideStage,
mDisplayImeController, mDisplayInsetsController, mSplitLayout, mTransitions,
- mTransactionPool,
- mLogger, Optional::empty);
+ mTransactionPool, mLogger, Optional.empty(), Optional::empty);
mSplitScreenTransitions = mStageCoordinator.getSplitTransitions();
doAnswer((Answer<IBinder>) invocation -> mock(IBinder.class))
.when(mTransitions).startTransition(anyInt(), any(), any());
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
index 58088d8..ad65c04 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
@@ -49,6 +49,7 @@
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.common.split.SplitLayout;
+import com.android.wm.shell.recents.RecentTasksController;
import com.android.wm.shell.transition.Transitions;
import org.junit.Before;
@@ -198,7 +199,8 @@
return new SplitTestUtils.TestStageCoordinator(mContext, DEFAULT_DISPLAY,
mSyncQueue, mRootTDAOrganizer, mTaskOrganizer, mMainStage, mSideStage,
mDisplayImeController, mDisplayInsetsController, splitLayout,
- mTransitions, mTransactionPool, mLogger, new UnfoldControllerProvider());
+ mTransitions, mTransactionPool, mLogger, Optional.empty(),
+ new UnfoldControllerProvider());
}
private class UnfoldControllerProvider implements