Loading libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java +38 −14 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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; Loading Loading @@ -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); Loading Loading @@ -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; } Loading @@ -206,7 +214,7 @@ class SplitScreenTransitions { if (mAnimatingTransition == mPendingEnter) { mPendingEnter = null; } if (mAnimatingTransition == mPendingDismiss) { if (mPendingDismiss != null && mPendingDismiss.mTransition == mAnimatingTransition) { mPendingDismiss = null; } mAnimatingTransition = null; Loading Loading @@ -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; } } } libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +99 −55 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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; Loading @@ -171,7 +166,6 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, setDividerVisibility(false); mSplitLayout.resetDividerPosition(); } mDismissTop = NO_DISMISS; }; private final SplitWindowManager.ParentContainerCallbacks mParentContainerCallbacks = Loading Loading @@ -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; } Loading Loading @@ -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 Loading Loading @@ -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), Loading @@ -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 (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 { if (triggerTask.getActivityType() == ACTIVITY_TYPE_HOME && isOpeningType(type)) { // Going home so dismiss both. mDismissTop = STAGE_TYPE_UNDEFINED; } // 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. if (isOpening && getStageOfTask(triggerTask) != null) { // One task is appearing into split, prepare to enter split screen. out = new WindowContainerTransaction(); mSplitTransitions.mPendingEnter = transition; mMainStage.activate(getMainStageBounds(), out, true /* includingTopTask */); mSideStage.moveToTop(getSideStageBounds(), out); } mSplitTransitions.mPendingEnter = transition; } } return out; Loading @@ -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. Loading Loading @@ -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; Loading Loading @@ -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 Loading Loading @@ -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 Loading @@ -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 */); Loading Loading @@ -1457,9 +1493,17 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, @Override public void onNoLongerSupportMultiWindow() { if (mMainStage.isActive()) { 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); } } private void reset() { Loading libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreenTransitions.java +0 −6 Original line number Diff line number Diff line Loading @@ -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); Loading libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java +3 −0 Original line number Diff line number Diff line Loading @@ -89,6 +89,9 @@ public class Transitions implements RemoteCallable<Transitions> { /** 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; Loading libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestRunningTaskInfoBuilder.java +9 −0 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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); Loading @@ -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; Loading @@ -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; Loading Loading
libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java +38 −14 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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; Loading Loading @@ -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); Loading Loading @@ -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; } Loading @@ -206,7 +214,7 @@ class SplitScreenTransitions { if (mAnimatingTransition == mPendingEnter) { mPendingEnter = null; } if (mAnimatingTransition == mPendingDismiss) { if (mPendingDismiss != null && mPendingDismiss.mTransition == mAnimatingTransition) { mPendingDismiss = null; } mAnimatingTransition = null; Loading Loading @@ -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; } } }
libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +99 −55 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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; Loading @@ -171,7 +166,6 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, setDividerVisibility(false); mSplitLayout.resetDividerPosition(); } mDismissTop = NO_DISMISS; }; private final SplitWindowManager.ParentContainerCallbacks mParentContainerCallbacks = Loading Loading @@ -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; } Loading Loading @@ -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 Loading Loading @@ -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), Loading @@ -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 (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 { if (triggerTask.getActivityType() == ACTIVITY_TYPE_HOME && isOpeningType(type)) { // Going home so dismiss both. mDismissTop = STAGE_TYPE_UNDEFINED; } // 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. if (isOpening && getStageOfTask(triggerTask) != null) { // One task is appearing into split, prepare to enter split screen. out = new WindowContainerTransaction(); mSplitTransitions.mPendingEnter = transition; mMainStage.activate(getMainStageBounds(), out, true /* includingTopTask */); mSideStage.moveToTop(getSideStageBounds(), out); } mSplitTransitions.mPendingEnter = transition; } } return out; Loading @@ -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. Loading Loading @@ -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; Loading Loading @@ -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 Loading Loading @@ -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 Loading @@ -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 */); Loading Loading @@ -1457,9 +1493,17 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, @Override public void onNoLongerSupportMultiWindow() { if (mMainStage.isActive()) { 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); } } private void reset() { Loading
libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreenTransitions.java +0 −6 Original line number Diff line number Diff line Loading @@ -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); Loading
libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java +3 −0 Original line number Diff line number Diff line Loading @@ -89,6 +89,9 @@ public class Transitions implements RemoteCallable<Transitions> { /** 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; Loading
libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestRunningTaskInfoBuilder.java +9 −0 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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); Loading @@ -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; Loading @@ -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; Loading