diff options
9 files changed, 138 insertions, 16 deletions
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 a56494943885..ac2e448eab58 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 @@ public abstract class WMShellBaseModule { @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 @@ public abstract class WMShellBaseModule { 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 3f17f2ba9394..6e38e421d4b6 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.internal.protolog.common.ProtoLog; 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 @@ public class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener { 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 @@ public class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener { }); mAnimatableTasksListener.onTaskAppeared(taskInfo); + updateRecentsForVisibleFullscreenTask(taskInfo); } @Override @@ -86,6 +96,7 @@ public class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener { 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 @@ public class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener { 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 83a0e6084acb..7457be2d0871 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.TransactionPool; 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,10 +125,12 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, 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, @@ -135,6 +138,19 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, 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; mContext = context; @@ -147,6 +163,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, mUnfoldControllerProvider = unfoldControllerProvider; mLogger = new SplitscreenEventLogger(); mIconProvider = iconProvider; + mRecentTasksOptional = recentTasks; } public SplitScreen asSplitScreen() { @@ -169,7 +186,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, 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 44af43d15d27..3c35e6a69bf5 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; 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 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, 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 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, 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 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, mRootTDAOrganizer = rootTDAOrganizer; mTaskOrganizer = taskOrganizer; mLogger = logger; + mRecentTasks = recentTasks; mMainUnfoldController = unfoldControllerProvider.get().orElse(null); mSideUnfoldController = unfoldControllerProvider.get().orElse(null); @@ -238,6 +246,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, 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 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, mMainUnfoldController = unfoldControllerProvider.get().orElse(null); mSideUnfoldController = unfoldControllerProvider.get().orElse(null); mLogger = logger; + mRecentTasks = recentTasks; transitions.addHandler(this); } @@ -560,12 +570,23 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, 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 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, } /** + * 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 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, 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 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, 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 5100c5625db6..190006ec0d9b 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 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener { } /** + * 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 d6f7e54ae369..9cbdf1e2dbb6 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 android.view.SurfaceControl; 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 @@ public class FullscreenTaskListenerTest { @Mock private FullscreenUnfoldController mUnfoldController; @Mock + private RecentTasksController mRecentTasksController; + @Mock private SurfaceControl mSurfaceControl; private Optional<FullscreenUnfoldController> mFullscreenUnfoldController; @@ -62,7 +65,8 @@ public class FullscreenTaskListenerTest { 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 f90af239db01..aab1e3a99c98 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.DisplayInsetsController; 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 @@ public class SplitTestUtils { 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 d5dee824ee9b..1eae625233a0 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.ShellExecutor; 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 @@ public class SplitTransitionTests extends ShellTestCase { 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 58088d8ab0dc..ad65c046be65 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.DisplayInsetsController; 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 @@ public class StageCoordinatorTests extends ShellTestCase { 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 |