From 7eccf61dcdf3a73c8dfc190e3273509f6f792d8c Mon Sep 17 00:00:00 2001 From: Jerry Chang Date: Tue, 14 Dec 2021 06:29:15 +0000 Subject: [PATCH] Integrate RecetsTaskController with shell-transition Hook up RecentsTaskController with split screen when enabled shell-transition. Bundle exit reason and dismiss transition together in order to log and determine whether the dismiss should break recorded app-pair in recents or not. When dismissing split screen with shell-transition, it'll reparent child tasks out of split screen before they committed invisible, so rely on fullscreen task opening signal to update app-pair information instead of relying on visible task appeared or changed in FullscreenTaskListener. Bug: 206487881 Test: atest SplitTransitionTests Test: dump and observe recorded split pair in RecentsTaskController Change-Id: I109d7b3b2eab2b4f691e85a2afd052f56b1a9bca --- .../splitscreen/SplitScreenTransitions.java | 52 ++++-- .../shell/splitscreen/StageCoordinator.java | 154 +++++++++++------- .../stagesplit/SplitScreenTransitions.java | 6 - .../wm/shell/transition/Transitions.java | 3 + .../wm/shell/TestRunningTaskInfoBuilder.java | 9 + .../splitscreen/SplitTransitionTests.java | 71 +++++++- 6 files changed, 217 insertions(+), 78 deletions(-) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java index 86e7b0e4cb7f0..16f4c7234e72d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java @@ -23,6 +23,10 @@ import static android.view.WindowManager.TRANSIT_TO_BACK; import static android.view.WindowManager.TRANSIT_TO_FRONT; import static android.window.TransitionInfo.FLAG_FIRST_CUSTOM; +import static com.android.wm.shell.splitscreen.SplitScreen.stageTypeToString; +import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DRAG_DIVIDER; +import static com.android.wm.shell.splitscreen.SplitScreenController.exitReasonToString; +import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_DISMISS; import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_DISMISS_SNAP; import static com.android.wm.shell.transition.Transitions.isOpeningType; @@ -40,7 +44,9 @@ import android.window.TransitionInfo; import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; +import com.android.internal.protolog.common.ProtoLog; import com.android.wm.shell.common.TransactionPool; +import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.transition.OneShotRemoteHandler; import com.android.wm.shell.transition.Transitions; @@ -57,7 +63,7 @@ class SplitScreenTransitions { private final Transitions mTransitions; private final Runnable mOnFinish; - IBinder mPendingDismiss = null; + DismissTransition mPendingDismiss = null; IBinder mPendingEnter = null; private IBinder mAnimatingTransition = null; @@ -127,12 +133,6 @@ class SplitScreenTransitions { } // TODO(shell-transitions): screenshot here final Rect startBounds = new Rect(change.getStartAbsBounds()); - if (info.getType() == TRANSIT_SPLIT_DISMISS_SNAP) { - // Dismissing split via snap which means the still-visible task has been - // dragged to its end position at animation start so reflect that here. - startBounds.offsetTo(change.getEndAbsBounds().left, - change.getEndAbsBounds().top); - } final Rect endBounds = new Rect(change.getEndAbsBounds()); startBounds.offset(-info.getRootOffset().x, -info.getRootOffset().y); endBounds.offset(-info.getRootOffset().x, -info.getRootOffset().y); @@ -184,12 +184,20 @@ class SplitScreenTransitions { return transition; } - /** Starts a transition for dismissing split after dragging the divider to a screen edge */ - IBinder startSnapToDismiss(@NonNull WindowContainerTransaction wct, - @NonNull Transitions.TransitionHandler handler) { - final IBinder transition = mTransitions.startTransition( - TRANSIT_SPLIT_DISMISS_SNAP, wct, handler); - mPendingDismiss = transition; + /** Starts a transition to dismiss split. */ + IBinder startDismissTransition(@Nullable IBinder transition, WindowContainerTransaction wct, + Transitions.TransitionHandler handler, @SplitScreen.StageType int dismissTop, + @SplitScreenController.ExitReason int reason) { + final int type = reason == EXIT_REASON_DRAG_DIVIDER + ? TRANSIT_SPLIT_DISMISS_SNAP : TRANSIT_SPLIT_DISMISS; + if (transition == null) { + transition = mTransitions.startTransition(type, wct, handler); + } + mPendingDismiss = new DismissTransition(transition, reason, dismissTop); + + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " splitTransition " + + " deduced Dismiss due to %s. toTop=%s", + exitReasonToString(reason), stageTypeToString(dismissTop)); return transition; } @@ -206,7 +214,7 @@ class SplitScreenTransitions { if (mAnimatingTransition == mPendingEnter) { mPendingEnter = null; } - if (mAnimatingTransition == mPendingDismiss) { + if (mPendingDismiss != null && mPendingDismiss.mTransition == mAnimatingTransition) { mPendingDismiss = null; } mAnimatingTransition = null; @@ -295,4 +303,20 @@ class SplitScreenTransitions { mAnimations.add(va); mTransitions.getAnimExecutor().execute(va::start); } + + /** Bundled information of dismiss transition. */ + static class DismissTransition { + IBinder mTransition; + + int mReason; + + @SplitScreen.StageType + int mDismissTop; + + DismissTransition(IBinder transition, int reason, int dismissTop) { + this.mTransition = transition; + this.mReason = reason; + this.mDismissTop = dismissTop; + } + } } 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 5e8f29f7cf18d..43a1d74ba49cb 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 @@ -18,9 +18,10 @@ 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_ASSISTANT; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER; -import static android.view.WindowManager.TRANSIT_OPEN; import static android.view.WindowManager.TRANSIT_TO_BACK; import static android.view.WindowManager.TRANSIT_TO_FRONT; import static android.view.WindowManager.transitTypeToString; @@ -32,7 +33,6 @@ import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSIT import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_MAIN; import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_SIDE; import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED; -import static com.android.wm.shell.splitscreen.SplitScreen.stageTypeToString; import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW; import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_APP_FINISHED; import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_CHILD_TASK_ENTER_PIP; @@ -40,6 +40,7 @@ import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DRAG_DIVIDER; import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_RETURN_HOME; import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP; +import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_UNKNOWN; import static com.android.wm.shell.splitscreen.SplitScreenController.exitReasonToString; import static com.android.wm.shell.splitscreen.SplitScreenTransitions.FLAG_IS_DIVIDER_BAR; import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS; @@ -121,9 +122,6 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, private static final String TAG = StageCoordinator.class.getSimpleName(); - /** internal value for mDismissTop that represents no dismiss */ - private static final int NO_DISMISS = -2; - private final SurfaceSession mSurfaceSession = new SurfaceSession(); private final MainStage mMainStage; @@ -156,9 +154,6 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, private boolean mKeyguardOccluded; private boolean mDeviceSleep; - @StageType - private int mDismissTop = NO_DISMISS; - /** The target stage to dismiss to when unlock after folded. */ @StageType private int mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED; @@ -171,7 +166,6 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, setDividerVisibility(false); mSplitLayout.resetDividerPosition(); } - mDismissTop = NO_DISMISS; }; private final SplitWindowManager.ParentContainerCallbacks mParentContainerCallbacks = @@ -461,8 +455,8 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, options = resolveStartStage(STAGE_TYPE_SIDE, position, options, wct); } } else { - // Exit split-screen and launch fullscreen since stage wasn't specified. - prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, wct); + Slog.w(TAG, + "No stage type nor split position specified to resolve start stage"); } break; } @@ -914,23 +908,22 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, } } - @VisibleForTesting - IBinder onSnappedToDismissTransition(boolean mainStageToTop) { - final WindowContainerTransaction wct = new WindowContainerTransaction(); - prepareExitSplitScreen(mainStageToTop ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE, wct); - return mSplitTransitions.startSnapToDismiss(wct, this); - } - @Override public void onSnappedToDismiss(boolean bottomOrRight) { final boolean mainStageToTop = bottomOrRight ? mSideStagePosition == SPLIT_POSITION_BOTTOM_OR_RIGHT : mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT; - if (ENABLE_SHELL_TRANSITIONS) { - onSnappedToDismissTransition(mainStageToTop); + + if (!ENABLE_SHELL_TRANSITIONS) { + exitSplitScreen(mainStageToTop ? mMainStage : mSideStage, EXIT_REASON_DRAG_DIVIDER); return; } - exitSplitScreen(mainStageToTop ? mMainStage : mSideStage, EXIT_REASON_DRAG_DIVIDER); + + final int dismissTop = mainStageToTop ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE; + final WindowContainerTransaction wct = new WindowContainerTransaction(); + prepareExitSplitScreen(dismissTop, wct); + mSplitTransitions.startDismissTransition( + null /* transition */, wct, this, dismissTop, EXIT_REASON_DRAG_DIVIDER); } @Override @@ -1109,14 +1102,25 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, @Nullable TransitionRequestInfo request) { final ActivityManager.RunningTaskInfo triggerTask = request.getTriggerTask(); if (triggerTask == null) { - // still want to monitor everything while in split-screen, so return non-null. + // Still want to monitor everything while in split-screen, so return non-null. return isSplitScreenVisible() ? new WindowContainerTransaction() : null; + } else if (triggerTask.displayId != mDisplayId) { + // Skip handling task on the other display. + return null; } WindowContainerTransaction out = null; final @WindowManager.TransitionType int type = request.getType(); + final boolean isOpening = isOpeningType(type); + final boolean inFullscreen = triggerTask.getWindowingMode() == WINDOWING_MODE_FULLSCREEN; + + if (isOpening && inFullscreen) { + // One task is opening into fullscreen mode, remove the corresponding split record. + mRecentTasks.ifPresent(recentTasks -> recentTasks.removeSplitPair(triggerTask.taskId)); + } + if (isSplitScreenVisible()) { - // try to handle everything while in split-screen, so return a WCT even if it's empty. + // Try to handle everything while in split-screen, so return a WCT even if it's empty. ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " split is active so using split" + "Transition to handle request. triggerTask=%d type=%s mainChildren=%d" + " sideChildren=%d", triggerTask.taskId, transitTypeToString(type), @@ -1124,35 +1128,35 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, out = new WindowContainerTransaction(); final StageTaskListener stage = getStageOfTask(triggerTask); if (stage != null) { - // dismiss split if the last task in one of the stages is going away + // Dismiss split if the last task in one of the stages is going away if (isClosingType(type) && stage.getChildCount() == 1) { // The top should be the opposite side that is closing: - mDismissTop = getStageType(stage) == STAGE_TYPE_MAIN - ? STAGE_TYPE_SIDE : STAGE_TYPE_MAIN; + int dismissTop = getStageType(stage) == STAGE_TYPE_MAIN ? STAGE_TYPE_SIDE + : STAGE_TYPE_MAIN; + prepareExitSplitScreen(dismissTop, out); + mSplitTransitions.startDismissTransition(transition, out, this, dismissTop, + EXIT_REASON_APP_FINISHED); } - } else { - if (triggerTask.getActivityType() == ACTIVITY_TYPE_HOME && isOpeningType(type)) { - // Going home so dismiss both. - mDismissTop = STAGE_TYPE_UNDEFINED; + } else if (isOpening && inFullscreen) { + final int activityType = triggerTask.getActivityType(); + if (activityType == ACTIVITY_TYPE_ASSISTANT) { + // We don't want assistant panel to dismiss split screen, so do nothing. + } else { + // Going home or occluded by the other fullscreen task, so dismiss both. + prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, out); + final int exitReason = activityType == ACTIVITY_TYPE_HOME + ? EXIT_REASON_RETURN_HOME : EXIT_REASON_UNKNOWN; + mSplitTransitions.startDismissTransition(transition, out, this, + STAGE_TYPE_UNDEFINED, exitReason); } } - if (mDismissTop != NO_DISMISS) { - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " splitTransition " - + " deduced Dismiss from request. toTop=%s", - stageTypeToString(mDismissTop)); - prepareExitSplitScreen(mDismissTop, out); - mSplitTransitions.mPendingDismiss = transition; - } } else { - // Not in split mode, so look for an open into a split stage to active split screen. - if ((type == TRANSIT_OPEN || type == TRANSIT_TO_FRONT)) { - if (getStageOfTask(triggerTask) != null) { - // One task is appearing in split, prepare to enter split screen. - out = new WindowContainerTransaction(); - mSplitTransitions.mPendingEnter = transition; - mMainStage.activate(getMainStageBounds(), out, true /* includingTopTask */); - mSideStage.moveToTop(getSideStageBounds(), out); - } + if (isOpening && getStageOfTask(triggerTask) != null) { + // One task is appearing into split, prepare to enter split screen. + out = new WindowContainerTransaction(); + mMainStage.activate(getMainStageBounds(), out, true /* includingTopTask */); + mSideStage.moveToTop(getSideStageBounds(), out); + mSplitTransitions.mPendingEnter = transition; } } return out; @@ -1164,8 +1168,9 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback) { - if (transition != mSplitTransitions.mPendingDismiss - && transition != mSplitTransitions.mPendingEnter) { + if (transition != mSplitTransitions.mPendingEnter && ( + mSplitTransitions.mPendingDismiss == null + || mSplitTransitions.mPendingDismiss.mTransition != transition)) { // Not entering or exiting, so just do some house-keeping and validation. // If we're not in split-mode, just abort so something else can handle it. @@ -1208,8 +1213,10 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, boolean shouldAnimate = true; if (mSplitTransitions.mPendingEnter == transition) { shouldAnimate = startPendingEnterAnimation(transition, info, startTransaction); - } else if (mSplitTransitions.mPendingDismiss == transition) { - shouldAnimate = startPendingDismissAnimation(transition, info, startTransaction); + } else if (mSplitTransitions.mPendingDismiss != null + && mSplitTransitions.mPendingDismiss.mTransition == transition) { + shouldAnimate = startPendingDismissAnimation( + mSplitTransitions.mPendingDismiss, info, startTransaction); } if (!shouldAnimate) return false; @@ -1263,10 +1270,22 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, + " to have been called with " + sideChild.getTaskInfo().taskId + " before startAnimation()."); } + + mShouldUpdateRecents = true; + updateRecentTasksSplitPair(); + + if (!mLogger.hasStartedSession()) { + mLogger.logEnter(mSplitLayout.getDividerPositionAsFraction(), + getMainStagePosition(), mMainStage.getTopChildTaskUid(), + getSideStagePosition(), mSideStage.getTopChildTaskUid(), + mSplitLayout.isLandscape()); + } + return true; } - private boolean startPendingDismissAnimation(@NonNull IBinder transition, + private boolean startPendingDismissAnimation( + @NonNull SplitScreenTransitions.DismissTransition dismissTransition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t) { // Make some noise if things aren't totally expected. These states shouldn't effect // transitions locally, but remotes (like Launcher) may get confused if they were @@ -1295,6 +1314,21 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, + "] before startAnimation()."); } + 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(dismissTransition.mReason) && mShouldUpdateRecents) { + for (TransitionInfo.Change change : info.getChanges()) { + final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo(); + if (taskInfo != null + && taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) { + recentTasks.removeSplitPair(taskInfo.taskId); + } + } + } + }); + mShouldUpdateRecents = false; + // Update local states. setSplitsVisible(false); // Wait until after animation to update divider @@ -1305,15 +1339,17 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, t.setWindowCrop(mSideStage.mRootLeash, null); } - if (mDismissTop == STAGE_TYPE_UNDEFINED) { - // Going home (dismissing both splits) - + if (dismissTransition.mDismissTop == STAGE_TYPE_UNDEFINED) { + logExit(dismissTransition.mReason); // TODO: Have a proper remote for this. Until then, though, reset state and use the // normal animation stuff (which falls back to the normal launcher remote). t.hide(mSplitLayout.getDividerLeash()); setDividerVisibility(false); mSplitTransitions.mPendingDismiss = null; return false; + } else { + logExitToStage(dismissTransition.mReason, + dismissTransition.mDismissTop == STAGE_TYPE_MAIN); } addDividerBarToTransition(info, t, false /* show */); @@ -1457,7 +1493,15 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, @Override public void onNoLongerSupportMultiWindow() { if (mMainStage.isActive()) { - StageCoordinator.this.exitSplitScreen(null /* childrenToTop */, + if (!ENABLE_SHELL_TRANSITIONS) { + StageCoordinator.this.exitSplitScreen(null /* childrenToTop */, + EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW); + } + + final WindowContainerTransaction wct = new WindowContainerTransaction(); + prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, wct); + mSplitTransitions.startDismissTransition(null /* transition */, wct, + StageCoordinator.this, STAGE_TYPE_UNDEFINED, EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreenTransitions.java index af9a5aa501e8b..0183654201773 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreenTransitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreenTransitions.java @@ -127,12 +127,6 @@ class SplitScreenTransitions { } // TODO(shell-transitions): screenshot here final Rect startBounds = new Rect(change.getStartAbsBounds()); - if (info.getType() == TRANSIT_SPLIT_DISMISS_SNAP) { - // Dismissing split via snap which means the still-visible task has been - // dragged to its end position at animation start so reflect that here. - startBounds.offsetTo(change.getEndAbsBounds().left, - change.getEndAbsBounds().top); - } final Rect endBounds = new Rect(change.getEndAbsBounds()); startBounds.offset(-info.getRootOffset().x, -info.getRootOffset().y); endBounds.offset(-info.getRootOffset().x, -info.getRootOffset().y); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java index a7a1788729053..b8cbfd9680fd0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java @@ -89,6 +89,9 @@ public class Transitions implements RemoteCallable { /** Transition type for entering split by opening an app into side-stage. */ public static final int TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE = TRANSIT_FIRST_CUSTOM + 5; + /** Transition type for dismissing split-screen. */ + public static final int TRANSIT_SPLIT_DISMISS = TRANSIT_FIRST_CUSTOM + 6; + private final WindowOrganizer mOrganizer; private final Context mContext; private final ShellExecutor mMainExecutor; diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestRunningTaskInfoBuilder.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestRunningTaskInfoBuilder.java index 2b5cd601b200e..51eec27cfc0e5 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestRunningTaskInfoBuilder.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestRunningTaskInfoBuilder.java @@ -18,6 +18,7 @@ package com.android.wm.shell; import static android.app.ActivityTaskManager.INVALID_TASK_ID; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; +import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; @@ -36,6 +37,7 @@ public final class TestRunningTaskInfoBuilder { private WindowContainerToken mToken = createMockWCToken(); private int mParentTaskId = INVALID_TASK_ID; private @WindowConfiguration.ActivityType int mActivityType = ACTIVITY_TYPE_STANDARD; + private @WindowConfiguration.WindowingMode int mWindowingMode = WINDOWING_MODE_UNDEFINED; public static WindowContainerToken createMockWCToken() { final IWindowContainerToken itoken = mock(IWindowContainerToken.class); @@ -60,6 +62,12 @@ public final class TestRunningTaskInfoBuilder { return this; } + public TestRunningTaskInfoBuilder setWindowingMode( + @WindowConfiguration.WindowingMode int windowingMode) { + mWindowingMode = windowingMode; + return this; + } + public ActivityManager.RunningTaskInfo build() { final ActivityManager.RunningTaskInfo info = new ActivityManager.RunningTaskInfo(); info.parentTaskId = INVALID_TASK_ID; @@ -67,6 +75,7 @@ public final class TestRunningTaskInfoBuilder { info.parentTaskId = mParentTaskId; info.configuration.windowConfiguration.setBounds(mBounds); info.configuration.windowConfiguration.setActivityType(mActivityType); + info.configuration.windowConfiguration.setWindowingMode(mWindowingMode); info.token = mToken; info.isResizeable = true; info.supportsMultiWindow = true; 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 c853fd350986b..be1ef091ce675 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 @@ -17,6 +17,7 @@ package com.android.wm.shell.splitscreen; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.WindowManager.TRANSIT_CHANGE; import static android.view.WindowManager.TRANSIT_CLOSE; @@ -26,6 +27,9 @@ import static android.view.WindowManager.TRANSIT_TO_FRONT; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER; +import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_SIDE; +import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW; +import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DRAG_DIVIDER; import static com.android.wm.shell.splitscreen.SplitTestUtils.createMockSurface; import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_SCREEN_PAIR_OPEN; @@ -249,7 +253,9 @@ public class SplitTransitionTests extends ShellTestCase { enterSplit(); ActivityManager.RunningTaskInfo homeTask = new TestRunningTaskInfoBuilder() - .setActivityType(ACTIVITY_TYPE_HOME).build(); + .setWindowingMode(WINDOWING_MODE_FULLSCREEN) + .setActivityType(ACTIVITY_TYPE_HOME) + .build(); // Create a request to bring home forward TransitionRequestInfo request = new TransitionRequestInfo(TRANSIT_TO_FRONT, homeTask, null); @@ -279,6 +285,64 @@ public class SplitTransitionTests extends ShellTestCase { assertFalse(mStageCoordinator.isSplitScreenVisible()); } + @Test + public void testDismissFromBeingOccluded() { + enterSplit(); + + ActivityManager.RunningTaskInfo normalTask = new TestRunningTaskInfoBuilder() + .setWindowingMode(WINDOWING_MODE_FULLSCREEN) + .build(); + + // Create a request to bring a normal task forward + TransitionRequestInfo request = + new TransitionRequestInfo(TRANSIT_TO_FRONT, normalTask, null); + IBinder transition = mock(IBinder.class); + WindowContainerTransaction result = mStageCoordinator.handleRequest(transition, request); + + assertTrue(containsSplitExit(result)); + + // make sure we haven't made any local changes yet (need to wait until transition is ready) + assertTrue(mStageCoordinator.isSplitScreenVisible()); + + // simulate the transition + TransitionInfo.Change normalChange = createChange(TRANSIT_TO_FRONT, normalTask); + TransitionInfo.Change mainChange = createChange(TRANSIT_TO_BACK, mMainChild); + TransitionInfo.Change sideChange = createChange(TRANSIT_TO_BACK, mSideChild); + + TransitionInfo info = new TransitionInfo(TRANSIT_TO_FRONT, 0); + info.addChange(normalChange); + info.addChange(mainChange); + info.addChange(sideChange); + mMainStage.onTaskVanished(mMainChild); + mSideStage.onTaskVanished(mSideChild); + mStageCoordinator.startAnimation(transition, info, + mock(SurfaceControl.Transaction.class), + mock(SurfaceControl.Transaction.class), + mock(Transitions.TransitionFinishCallback.class)); + assertFalse(mStageCoordinator.isSplitScreenVisible()); + } + + @Test + public void testDismissFromMultiWindowSupport() { + enterSplit(); + + // simulate the transition + TransitionInfo.Change mainChange = createChange(TRANSIT_TO_BACK, mMainChild); + TransitionInfo.Change sideChange = createChange(TRANSIT_TO_BACK, mSideChild); + TransitionInfo info = new TransitionInfo(TRANSIT_TO_BACK, 0); + info.addChange(mainChange); + info.addChange(sideChange); + IBinder transition = mSplitScreenTransitions.startDismissTransition(null, + new WindowContainerTransaction(), mStageCoordinator, + EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW, STAGE_TYPE_SIDE); + boolean accepted = mStageCoordinator.startAnimation(transition, info, + mock(SurfaceControl.Transaction.class), + mock(SurfaceControl.Transaction.class), + mock(Transitions.TransitionFinishCallback.class)); + assertTrue(accepted); + assertFalse(mStageCoordinator.isSplitScreenVisible()); + } + @Test public void testDismissSnap() { enterSplit(); @@ -290,8 +354,9 @@ public class SplitTransitionTests extends ShellTestCase { TransitionInfo info = new TransitionInfo(TRANSIT_TO_BACK, 0); info.addChange(mainChange); info.addChange(sideChange); - IBinder transition = mStageCoordinator.onSnappedToDismissTransition( - false /* mainStageToTop */); + IBinder transition = mSplitScreenTransitions.startDismissTransition(null, + new WindowContainerTransaction(), mStageCoordinator, EXIT_REASON_DRAG_DIVIDER, + STAGE_TYPE_SIDE); mMainStage.onTaskVanished(mMainChild); mSideStage.onTaskVanished(mSideChild); boolean accepted = mStageCoordinator.startAnimation(transition, info,