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 1cf3a896b68ea..2b21e77be9c13 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 @@ -22,6 +22,8 @@ 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 com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_MAIN; +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_DRAG_DIVIDER; import static com.android.wm.shell.splitscreen.SplitScreenController.exitReasonToString; @@ -179,6 +181,33 @@ class SplitScreenTransitions { onFinish(null /* wct */, null /* wctCB */); } + void applyDismissTransition(@NonNull IBinder transition, @NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction startTransaction, + @NonNull SurfaceControl.Transaction finishTransaction, + @NonNull Transitions.TransitionFinishCallback finishCallback, + @NonNull WindowContainerToken topRoot, + @NonNull WindowContainerToken mainRoot, @NonNull WindowContainerToken sideRoot, + @NonNull SplitDecorManager mainDecor, @NonNull SplitDecorManager sideDecor) { + if (mPendingDismiss.mDismissTop != STAGE_TYPE_UNDEFINED) { + mFinishCallback = finishCallback; + mAnimatingTransition = transition; + mFinishTransaction = finishTransaction; + + final SplitDecorManager topDecor = mPendingDismiss.mDismissTop == STAGE_TYPE_MAIN + ? mainDecor : sideDecor; + topDecor.fadeOutDecor(() -> { + mTransitions.getMainExecutor().execute(() -> { + onFinish(null /* wct */, null /* wctCB */); + }); + }); + + startTransaction.apply(); + } else { + playAnimation(transition, info, startTransaction, finishTransaction, + finishCallback, mainRoot, sideRoot, topRoot); + } + } + void applyResizeTransition(@NonNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @@ -200,8 +229,11 @@ class SplitScreenTransitions { SplitDecorManager decor = mainRoot.equals(change.getContainer()) ? mainDecor : sideDecor; + + // This is to ensure onFinished be called after all animations ended. ValueAnimator va = new ValueAnimator(); mAnimations.add(va); + decor.setScreenshotIfNeeded(change.getSnapshot(), startTransaction); decor.onResized(startTransaction, () -> { mTransitions.getMainExecutor().execute(() -> { 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 6f13ed98a130a..5af75981dcad2 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 @@ -1354,8 +1354,18 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, private void prepareExitSplitScreen(@StageType int stageToTop, @NonNull WindowContainerTransaction wct) { if (!mMainStage.isActive()) return; - mSideStage.removeAllTasks(wct, stageToTop == STAGE_TYPE_SIDE); - mMainStage.deactivate(wct, stageToTop == STAGE_TYPE_MAIN); + // Set the dismiss-to-top side to fullscreen for dismiss transition. + // Reparent the non-dismiss-to-top side to properly update its visibility. + if (stageToTop == STAGE_TYPE_MAIN) { + wct.setBounds(mMainStage.mRootTaskInfo.token, null /* bounds */); + mSideStage.removeAllTasks(wct, false /* toTop */); + } else if (stageToTop == STAGE_TYPE_SIDE) { + wct.setBounds(mSideStage.mRootTaskInfo.token, null /* bounds */); + mMainStage.deactivate(wct, false /* toTop */); + } else { + mSideStage.removeAllTasks(wct, false /* toTop */); + mMainStage.deactivate(wct, false /* toTop */); + } } private void prepareEnterSplitScreen(WindowContainerTransaction wct) { @@ -2273,6 +2283,13 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } else if (mSplitTransitions.isPendingDismiss(transition)) { shouldAnimate = startPendingDismissAnimation( mSplitTransitions.mPendingDismiss, info, startTransaction, finishTransaction); + if (shouldAnimate) { + mSplitTransitions.applyDismissTransition(transition, info, + startTransaction, finishTransaction, finishCallback, mRootTaskInfo.token, + mMainStage.mRootTaskInfo.token, mSideStage.mRootTaskInfo.token, + mMainStage.getSplitDecorManager(), mSideStage.getSplitDecorManager()); + return true; + } } else if (mSplitTransitions.isPendingResize(transition)) { mSplitTransitions.applyResizeTransition(transition, info, startTransaction, finishTransaction, finishCallback, mMainStage.mRootTaskInfo.token, @@ -2403,37 +2420,44 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, // aren't serialized with transition callbacks. // TODO(b/184679596): Find a way to either include task-org information in // the transition, or synchronize task-org callbacks. - if (mMainStage.getChildCount() != 0) { - final StringBuilder tasksLeft = new StringBuilder(); - for (int i = 0; i < mMainStage.getChildCount(); ++i) { - tasksLeft.append(i != 0 ? ", " : ""); - tasksLeft.append(mMainStage.mChildrenTaskInfo.keyAt(i)); + if (toStage == STAGE_TYPE_UNDEFINED) { + if (mMainStage.getChildCount() != 0) { + final StringBuilder tasksLeft = new StringBuilder(); + for (int i = 0; i < mMainStage.getChildCount(); ++i) { + tasksLeft.append(i != 0 ? ", " : ""); + tasksLeft.append(mMainStage.mChildrenTaskInfo.keyAt(i)); + } + Log.w(TAG, "Expected onTaskVanished on " + mMainStage + + " to have been called with [" + tasksLeft.toString() + + "] before startAnimation()."); } - Log.w(TAG, "Expected onTaskVanished on " + mMainStage - + " to have been called with [" + tasksLeft.toString() - + "] before startAnimation()."); - } - if (mSideStage.getChildCount() != 0) { - final StringBuilder tasksLeft = new StringBuilder(); - for (int i = 0; i < mSideStage.getChildCount(); ++i) { - tasksLeft.append(i != 0 ? ", " : ""); - tasksLeft.append(mSideStage.mChildrenTaskInfo.keyAt(i)); + if (mSideStage.getChildCount() != 0) { + final StringBuilder tasksLeft = new StringBuilder(); + for (int i = 0; i < mSideStage.getChildCount(); ++i) { + tasksLeft.append(i != 0 ? ", " : ""); + tasksLeft.append(mSideStage.mChildrenTaskInfo.keyAt(i)); + } + Log.w(TAG, "Expected onTaskVanished on " + mSideStage + + " to have been called with [" + tasksLeft.toString() + + "] before startAnimation()."); } - Log.w(TAG, "Expected onTaskVanished on " + mSideStage - + " to have been called with [" + tasksLeft.toString() - + "] 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(dismissReason) && 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); + if (toStage == STAGE_TYPE_UNDEFINED) { + for (TransitionInfo.Change change : info.getChanges()) { + final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo(); + if (taskInfo != null + && taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) { + recentTasks.removeSplitPair(taskInfo.taskId); + } } + } else { + recentTasks.removeSplitPair(mMainStage.getTopVisibleChildTaskId()); + recentTasks.removeSplitPair(mSideStage.getTopVisibleChildTaskId()); } } }); @@ -2446,6 +2470,12 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, // Reset crops so they don't interfere with subsequent launches t.setCrop(mMainStage.mRootLeash, null); t.setCrop(mSideStage.mRootLeash, null); + // Hide the non-top stage and set the top one to the fullscreen position. + if (toStage != STAGE_TYPE_UNDEFINED) { + t.hide(toStage == STAGE_TYPE_MAIN ? mSideStage.mRootLeash : mMainStage.mRootLeash); + t.setPosition(toStage == STAGE_TYPE_MAIN + ? mMainStage.mRootLeash : mSideStage.mRootLeash, 0, 0); + } if (toStage == STAGE_TYPE_UNDEFINED) { logExit(dismissReason); @@ -2472,6 +2502,18 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mSplitLayout.release(t); mSplitTransitions.mPendingDismiss = null; return false; + } else { + final @SplitScreen.StageType int dismissTop = dismissTransition.mDismissTop; + // Reparent all tasks after dismiss transition finished. + dismissTransition.setFinishedCallback( + new SplitScreenTransitions.TransitionFinishedCallback() { + @Override + public void onFinished(WindowContainerTransaction wct, + SurfaceControl.Transaction t) { + mSideStage.removeAllTasks(wct, dismissTop == STAGE_TYPE_SIDE); + mMainStage.deactivate(wct, dismissTop == STAGE_TYPE_MAIN); + } + }); } addDividerBarToTransition(info, finishT, false /* show */); 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 e9f1df221f38f..ed0ac5f1cdca5 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 @@ -371,7 +371,8 @@ public class SplitTransitionTests extends ShellTestCase { IBinder transition = mock(IBinder.class); WindowContainerTransaction result = mStageCoordinator.handleRequest(transition, request); - assertTrue(containsSplitExit(result)); + // Don't reparent tasks until the animation is complete. + assertFalse(containsSplitExit(result)); // make sure we haven't made any local changes yet (need to wait until transition is ready) assertTrue(mStageCoordinator.isSplitScreenVisible());