Loading libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +96 −23 Original line number Diff line number Diff line Loading @@ -48,6 +48,7 @@ import static com.android.wm.shell.common.split.SplitScreenUtils.reverseSplitPos import static com.android.wm.shell.common.split.SplitScreenUtils.splitFailureMessage; import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN; import static com.android.wm.shell.shared.TransitionUtil.isClosingType; import static com.android.wm.shell.shared.TransitionUtil.isOpeningMode; import static com.android.wm.shell.shared.TransitionUtil.isOpeningType; import static com.android.wm.shell.shared.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR; import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_10_90; Loading Loading @@ -1619,21 +1620,40 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, * not in split screen, {@link #mLastActiveStage} should be set to STAGE_TYPE_UNDEFINED, and we * will do a no-op. */ void dismissSplitKeepingLastActiveStage(@ExitReason int reason) { if (!isSplitActive() || mLastActiveStage == STAGE_TYPE_UNDEFINED) { void dismissSplitKeepingLastActiveStage(@ExitReason int exitReason) { if (mLastActiveStage == STAGE_TYPE_UNDEFINED) { // no-op return; } // Need manually clear here due to this transition might be aborted due to keyguard // on top and lead to no visible change. clearSplitPairedInRecents(reason); dismissSplit(mLastActiveStage, exitReason); mBreakOnNextWake = false; } /** * Dismisses split in the background. */ public void dismissSplitInBackground(@ExitReason int exitReason) { dismissSplit(STAGE_TYPE_UNDEFINED, exitReason); } /** * Starts a new transition to dismiss split. */ private void dismissSplit(@StageType int stageToTop, @ExitReason int exitReason) { if (!isSplitActive()) { return; } ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "dismissSplit: stageToTop=%s reason=%s", stageTypeToString(stageToTop), exitReasonToString(exitReason)); // Need manually clear here due to this transition might be aborted due to no visible change clearSplitPairedInRecents(exitReason); final WindowContainerTransaction wct = new WindowContainerTransaction(); prepareExitSplitScreen(mLastActiveStage, wct, reason); mSplitTransitions.startDismissTransition(wct, this, mLastActiveStage, reason); prepareExitSplitScreen(stageToTop, wct, exitReason); mSplitTransitions.startDismissTransition(wct, this, stageToTop, exitReason); setSplitsVisible(false); mBreakOnNextWake = false; logExit(reason); logExit(exitReason); } void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) { Loading Loading @@ -3175,20 +3195,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, // the remote handler. return null; } boolean anyStageContainsSingleFullscreenTask; if (enableFlexibleSplit()) { anyStageContainsSingleFullscreenTask = mStageOrderOperator.getActiveStages().stream() .anyMatch(stageListener -> stageListener.containsTask(triggerTask.taskId) && stageListener.getChildCount() == 1); } else { anyStageContainsSingleFullscreenTask = (mMainStage.containsTask(triggerTask.taskId) && mMainStage.getChildCount() == 1) || (mSideStage.containsTask(triggerTask.taskId) && mSideStage.getChildCount() == 1); } boolean anyStageContainsSingleFullscreenTask = isLastTaskInAnyStage( triggerTask.taskId); if (anyStageContainsSingleFullscreenTask) { // A splitting task is opening to fullscreen causes one side of the split empty, // so appends operations to exit split. Loading Loading @@ -3249,6 +3257,23 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, return out; } /** * @return The provided taskId is the last child of any stage. */ private boolean isLastTaskInAnyStage(int taskId) { if (enableFlexibleSplit()) { return mStageOrderOperator.getActiveStages().stream() .anyMatch(stageListener -> stageListener.containsTask(taskId) && stageListener.getChildCount() == 1); } else { return (mMainStage.containsTask(taskId) && mMainStage.getChildCount() == 1) || (mSideStage.containsTask(taskId) && mSideStage.getChildCount() == 1); } } /** @return whether the transition-request implies entering pip from split. */ public boolean requestImpliesSplitToPip(TransitionRequestInfo request) { final TaskInfo triggerTask = request.getTriggerTask(); Loading Loading @@ -3398,6 +3423,53 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, prepareExitSplitScreen(topStage, outWCT, EXIT_REASON_UNKNOWN); } /** * When we launch an app, there are times when the app trampolines itself into another activity * and ends up breaking a split pair (because that second activity already existed as part of a * pair). This method is used to detect whether that happened, so we can clean up the split * state. * @return whether the transition implies a split task being launched in fullscreen resulting * in splitscreen being broken. */ public boolean transitionImpliesSplitToFullscreen(TransitionInfo info) { if (!isSplitActive() || isSplitScreenVisible()) { // Not in split, or if visible then we would have already handled it. return false; } for (int i = 0; i < info.getChanges().size(); i++) { final TransitionInfo.Change change = info.getChanges().get(i); final TaskInfo task = change.getTaskInfo(); if (task == null || task.getActivityType() == ACTIVITY_TYPE_HOME || task.getActivityType() == ACTIVITY_TYPE_RECENTS) { continue; } final boolean isOpening = isOpeningMode(change.getMode()); final boolean inFullscreen = task.getWindowingMode() == WINDOWING_MODE_FULLSCREEN; if (isOpening && inFullscreen) { // Note: currently we still rely on non-transition signals to update the stage // children so we may end up with an empty stage, but once we migrate away from that // we'll actually need to check that the task being launched into fullscreen is // actually the last child of a stage final boolean hasEmptyStage; if (enableFlexibleSplit()) { hasEmptyStage = mStageOrderOperator.getActiveStages().stream() .anyMatch(stage -> stage.getChildCount() == 0); } else { hasEmptyStage = mMainStage.getChildCount() == 0 || mSideStage.getChildCount() == 0; } if (isLastTaskInAnyStage(task.taskId) || hasEmptyStage) { ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "Fullscreen task launch transition will break split"); return true; } } } return false; } @Override public void mergeAnimation(IBinder transition, TransitionInfo info, @NonNull SurfaceControl.Transaction startT, Loading Loading @@ -4408,6 +4480,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, pw.println(prefix + TAG + " mDisplayId=" + mDisplayId); pw.println(innerPrefix + "mDividerVisible=" + mDividerVisible); pw.println(innerPrefix + "isSplitActive=" + isSplitActive()); pw.println(innerPrefix + "mLastActiveStage=" + mLastActiveStage); pw.println(innerPrefix + "isSplitVisible=" + isSplitScreenVisible()); pw.println(innerPrefix + "isLeftRightSplit=" + (mSplitLayout != null ? isLeftRightSplit() : "null")); Loading libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java +17 −4 Original line number Diff line number Diff line Loading @@ -21,6 +21,7 @@ import static android.view.WindowManager.TRANSIT_CHANGE; import static android.view.WindowManager.TRANSIT_PIP; import static android.view.WindowManager.TRANSIT_TO_BACK; import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_FULLSCREEN_REQUEST; import static com.android.wm.shell.transition.DefaultMixedHandler.subCopy; import static com.android.wm.shell.transition.MixedTransitionHelper.animateEnterPipFromSplit; import static com.android.wm.shell.transition.MixedTransitionHelper.animateKeyguard; Loading Loading @@ -113,7 +114,7 @@ class DefaultMixedTransition extends DefaultMixedHandler.MixedTransition { mKeyguardHandler, mPipHandler); case TYPE_OPTIONS_REMOTE_AND_PIP_OR_DESKTOP_CHANGE -> animateOpenIntentWithRemoteAndPipOrDesktop(transition, info, startTransaction, finishTransaction, finishCallback); finishTransaction, finishCallback, mSplitHandler); case TYPE_UNFOLD -> animateUnfold(transition, info, startTransaction, finishTransaction, finishCallback); Loading Loading @@ -197,11 +198,12 @@ class DefaultMixedTransition extends DefaultMixedHandler.MixedTransition { @NonNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback) { @NonNull Transitions.TransitionFinishCallback finishCallback, @NonNull StageCoordinator splitHandler) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Mixed transition for opening an intent" + " with a remote transition and PIP or Desktop #%d", info.getDebugId()); boolean handledToPipOrDesktop = tryAnimateOpenIntentWithRemoteAndPipOrDesktop( info, startTransaction, finishTransaction, finishCallback); info, startTransaction, finishTransaction, finishCallback, splitHandler); // Consume the transition on remote handler if the leftover handler already handle this // transition. And if it cannot, the transition will be handled by remote handler, so don't // consume here. Loading @@ -218,9 +220,20 @@ class DefaultMixedTransition extends DefaultMixedHandler.MixedTransition { @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback) { @NonNull Transitions.TransitionFinishCallback finishCallback, @NonNull StageCoordinator splitHandler) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "tryAnimateOpenIntentWithRemoteAndPipOrDesktop"); // This is specifically for handling trampolines w/ previously split tasks. In the case that // an activity (which will trampoline into a previously split task) is launched into // fullscreen, the StageCoordinator does not have enough info at transition-request time to // decide whether to handle the transition and never gets a chance to clean up the split // state. So we check here to see if that happened, and clean up if so. if (splitHandler.transitionImpliesSplitToFullscreen(info)) { splitHandler.dismissSplitInBackground(EXIT_REASON_FULLSCREEN_REQUEST); } TransitionInfo.Change pipChange = null; TransitionInfo.Change pipActivityChange = null; for (int i = info.getChanges().size() - 1; i >= 0; --i) { Loading Loading
libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +96 −23 Original line number Diff line number Diff line Loading @@ -48,6 +48,7 @@ import static com.android.wm.shell.common.split.SplitScreenUtils.reverseSplitPos import static com.android.wm.shell.common.split.SplitScreenUtils.splitFailureMessage; import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN; import static com.android.wm.shell.shared.TransitionUtil.isClosingType; import static com.android.wm.shell.shared.TransitionUtil.isOpeningMode; import static com.android.wm.shell.shared.TransitionUtil.isOpeningType; import static com.android.wm.shell.shared.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR; import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_10_90; Loading Loading @@ -1619,21 +1620,40 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, * not in split screen, {@link #mLastActiveStage} should be set to STAGE_TYPE_UNDEFINED, and we * will do a no-op. */ void dismissSplitKeepingLastActiveStage(@ExitReason int reason) { if (!isSplitActive() || mLastActiveStage == STAGE_TYPE_UNDEFINED) { void dismissSplitKeepingLastActiveStage(@ExitReason int exitReason) { if (mLastActiveStage == STAGE_TYPE_UNDEFINED) { // no-op return; } // Need manually clear here due to this transition might be aborted due to keyguard // on top and lead to no visible change. clearSplitPairedInRecents(reason); dismissSplit(mLastActiveStage, exitReason); mBreakOnNextWake = false; } /** * Dismisses split in the background. */ public void dismissSplitInBackground(@ExitReason int exitReason) { dismissSplit(STAGE_TYPE_UNDEFINED, exitReason); } /** * Starts a new transition to dismiss split. */ private void dismissSplit(@StageType int stageToTop, @ExitReason int exitReason) { if (!isSplitActive()) { return; } ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "dismissSplit: stageToTop=%s reason=%s", stageTypeToString(stageToTop), exitReasonToString(exitReason)); // Need manually clear here due to this transition might be aborted due to no visible change clearSplitPairedInRecents(exitReason); final WindowContainerTransaction wct = new WindowContainerTransaction(); prepareExitSplitScreen(mLastActiveStage, wct, reason); mSplitTransitions.startDismissTransition(wct, this, mLastActiveStage, reason); prepareExitSplitScreen(stageToTop, wct, exitReason); mSplitTransitions.startDismissTransition(wct, this, stageToTop, exitReason); setSplitsVisible(false); mBreakOnNextWake = false; logExit(reason); logExit(exitReason); } void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) { Loading Loading @@ -3175,20 +3195,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, // the remote handler. return null; } boolean anyStageContainsSingleFullscreenTask; if (enableFlexibleSplit()) { anyStageContainsSingleFullscreenTask = mStageOrderOperator.getActiveStages().stream() .anyMatch(stageListener -> stageListener.containsTask(triggerTask.taskId) && stageListener.getChildCount() == 1); } else { anyStageContainsSingleFullscreenTask = (mMainStage.containsTask(triggerTask.taskId) && mMainStage.getChildCount() == 1) || (mSideStage.containsTask(triggerTask.taskId) && mSideStage.getChildCount() == 1); } boolean anyStageContainsSingleFullscreenTask = isLastTaskInAnyStage( triggerTask.taskId); if (anyStageContainsSingleFullscreenTask) { // A splitting task is opening to fullscreen causes one side of the split empty, // so appends operations to exit split. Loading Loading @@ -3249,6 +3257,23 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, return out; } /** * @return The provided taskId is the last child of any stage. */ private boolean isLastTaskInAnyStage(int taskId) { if (enableFlexibleSplit()) { return mStageOrderOperator.getActiveStages().stream() .anyMatch(stageListener -> stageListener.containsTask(taskId) && stageListener.getChildCount() == 1); } else { return (mMainStage.containsTask(taskId) && mMainStage.getChildCount() == 1) || (mSideStage.containsTask(taskId) && mSideStage.getChildCount() == 1); } } /** @return whether the transition-request implies entering pip from split. */ public boolean requestImpliesSplitToPip(TransitionRequestInfo request) { final TaskInfo triggerTask = request.getTriggerTask(); Loading Loading @@ -3398,6 +3423,53 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, prepareExitSplitScreen(topStage, outWCT, EXIT_REASON_UNKNOWN); } /** * When we launch an app, there are times when the app trampolines itself into another activity * and ends up breaking a split pair (because that second activity already existed as part of a * pair). This method is used to detect whether that happened, so we can clean up the split * state. * @return whether the transition implies a split task being launched in fullscreen resulting * in splitscreen being broken. */ public boolean transitionImpliesSplitToFullscreen(TransitionInfo info) { if (!isSplitActive() || isSplitScreenVisible()) { // Not in split, or if visible then we would have already handled it. return false; } for (int i = 0; i < info.getChanges().size(); i++) { final TransitionInfo.Change change = info.getChanges().get(i); final TaskInfo task = change.getTaskInfo(); if (task == null || task.getActivityType() == ACTIVITY_TYPE_HOME || task.getActivityType() == ACTIVITY_TYPE_RECENTS) { continue; } final boolean isOpening = isOpeningMode(change.getMode()); final boolean inFullscreen = task.getWindowingMode() == WINDOWING_MODE_FULLSCREEN; if (isOpening && inFullscreen) { // Note: currently we still rely on non-transition signals to update the stage // children so we may end up with an empty stage, but once we migrate away from that // we'll actually need to check that the task being launched into fullscreen is // actually the last child of a stage final boolean hasEmptyStage; if (enableFlexibleSplit()) { hasEmptyStage = mStageOrderOperator.getActiveStages().stream() .anyMatch(stage -> stage.getChildCount() == 0); } else { hasEmptyStage = mMainStage.getChildCount() == 0 || mSideStage.getChildCount() == 0; } if (isLastTaskInAnyStage(task.taskId) || hasEmptyStage) { ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "Fullscreen task launch transition will break split"); return true; } } } return false; } @Override public void mergeAnimation(IBinder transition, TransitionInfo info, @NonNull SurfaceControl.Transaction startT, Loading Loading @@ -4408,6 +4480,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, pw.println(prefix + TAG + " mDisplayId=" + mDisplayId); pw.println(innerPrefix + "mDividerVisible=" + mDividerVisible); pw.println(innerPrefix + "isSplitActive=" + isSplitActive()); pw.println(innerPrefix + "mLastActiveStage=" + mLastActiveStage); pw.println(innerPrefix + "isSplitVisible=" + isSplitScreenVisible()); pw.println(innerPrefix + "isLeftRightSplit=" + (mSplitLayout != null ? isLeftRightSplit() : "null")); Loading
libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedTransition.java +17 −4 Original line number Diff line number Diff line Loading @@ -21,6 +21,7 @@ import static android.view.WindowManager.TRANSIT_CHANGE; import static android.view.WindowManager.TRANSIT_PIP; import static android.view.WindowManager.TRANSIT_TO_BACK; import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_FULLSCREEN_REQUEST; import static com.android.wm.shell.transition.DefaultMixedHandler.subCopy; import static com.android.wm.shell.transition.MixedTransitionHelper.animateEnterPipFromSplit; import static com.android.wm.shell.transition.MixedTransitionHelper.animateKeyguard; Loading Loading @@ -113,7 +114,7 @@ class DefaultMixedTransition extends DefaultMixedHandler.MixedTransition { mKeyguardHandler, mPipHandler); case TYPE_OPTIONS_REMOTE_AND_PIP_OR_DESKTOP_CHANGE -> animateOpenIntentWithRemoteAndPipOrDesktop(transition, info, startTransaction, finishTransaction, finishCallback); finishTransaction, finishCallback, mSplitHandler); case TYPE_UNFOLD -> animateUnfold(transition, info, startTransaction, finishTransaction, finishCallback); Loading Loading @@ -197,11 +198,12 @@ class DefaultMixedTransition extends DefaultMixedHandler.MixedTransition { @NonNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback) { @NonNull Transitions.TransitionFinishCallback finishCallback, @NonNull StageCoordinator splitHandler) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Mixed transition for opening an intent" + " with a remote transition and PIP or Desktop #%d", info.getDebugId()); boolean handledToPipOrDesktop = tryAnimateOpenIntentWithRemoteAndPipOrDesktop( info, startTransaction, finishTransaction, finishCallback); info, startTransaction, finishTransaction, finishCallback, splitHandler); // Consume the transition on remote handler if the leftover handler already handle this // transition. And if it cannot, the transition will be handled by remote handler, so don't // consume here. Loading @@ -218,9 +220,20 @@ class DefaultMixedTransition extends DefaultMixedHandler.MixedTransition { @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback) { @NonNull Transitions.TransitionFinishCallback finishCallback, @NonNull StageCoordinator splitHandler) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "tryAnimateOpenIntentWithRemoteAndPipOrDesktop"); // This is specifically for handling trampolines w/ previously split tasks. In the case that // an activity (which will trampoline into a previously split task) is launched into // fullscreen, the StageCoordinator does not have enough info at transition-request time to // decide whether to handle the transition and never gets a chance to clean up the split // state. So we check here to see if that happened, and clean up if so. if (splitHandler.transitionImpliesSplitToFullscreen(info)) { splitHandler.dismissSplitInBackground(EXIT_REASON_FULLSCREEN_REQUEST); } TransitionInfo.Change pipChange = null; TransitionInfo.Change pipActivityChange = null; for (int i = info.getChanges().size() - 1; i >= 0; --i) { Loading