Loading libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java +32 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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, Loading @@ -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(() -> { Loading libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +66 −24 Original line number Diff line number Diff line Loading @@ -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) { Loading Loading @@ -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, Loading Loading @@ -2403,6 +2420,7 @@ 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 (toStage == STAGE_TYPE_UNDEFINED) { if (mMainStage.getChildCount() != 0) { final StringBuilder tasksLeft = new StringBuilder(); for (int i = 0; i < mMainStage.getChildCount(); ++i) { Loading @@ -2423,11 +2441,13 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, + " 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) { if (toStage == STAGE_TYPE_UNDEFINED) { for (TransitionInfo.Change change : info.getChanges()) { final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo(); if (taskInfo != null Loading @@ -2435,6 +2455,10 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, recentTasks.removeSplitPair(taskInfo.taskId); } } } else { recentTasks.removeSplitPair(mMainStage.getTopVisibleChildTaskId()); recentTasks.removeSplitPair(mSideStage.getTopVisibleChildTaskId()); } } }); mShouldUpdateRecents = false; Loading @@ -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); Loading @@ -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 */); Loading libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java +2 −1 Original line number Diff line number Diff line Loading @@ -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()); Loading Loading
libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java +32 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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, Loading @@ -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(() -> { Loading
libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +66 −24 Original line number Diff line number Diff line Loading @@ -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) { Loading Loading @@ -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, Loading Loading @@ -2403,6 +2420,7 @@ 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 (toStage == STAGE_TYPE_UNDEFINED) { if (mMainStage.getChildCount() != 0) { final StringBuilder tasksLeft = new StringBuilder(); for (int i = 0; i < mMainStage.getChildCount(); ++i) { Loading @@ -2423,11 +2441,13 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, + " 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) { if (toStage == STAGE_TYPE_UNDEFINED) { for (TransitionInfo.Change change : info.getChanges()) { final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo(); if (taskInfo != null Loading @@ -2435,6 +2455,10 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, recentTasks.removeSplitPair(taskInfo.taskId); } } } else { recentTasks.removeSplitPair(mMainStage.getTopVisibleChildTaskId()); recentTasks.removeSplitPair(mSideStage.getTopVisibleChildTaskId()); } } }); mShouldUpdateRecents = false; Loading @@ -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); Loading @@ -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 */); Loading
libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java +2 −1 Original line number Diff line number Diff line Loading @@ -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()); Loading