Loading libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java +19 −41 Original line number Diff line number Diff line Loading @@ -352,15 +352,6 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, public void startIntent(PendingIntent intent, @Nullable Intent fillInIntent, @SplitPosition int position, @Nullable Bundle options) { if (!ENABLE_SHELL_TRANSITIONS) { startIntentLegacy(intent, fillInIntent, position, options); return; } try { options = mStageCoordinator.resolveStartStage(STAGE_TYPE_UNDEFINED, position, options, null /* wct */); if (fillInIntent == null) { fillInIntent = new Intent(); } Loading @@ -376,17 +367,16 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK"); } intent.send(mContext, 0, fillInIntent, null /* onFinished */, null /* handler */, null /* requiredPermission */, options); } catch (PendingIntent.CanceledException e) { Slog.e(TAG, "Failed to launch task", e); if (!ENABLE_SHELL_TRANSITIONS) { startIntentLegacy(intent, fillInIntent, position, options); return; } mStageCoordinator.startIntent(intent, fillInIntent, position, options); } private void startIntentLegacy(PendingIntent intent, @Nullable Intent fillInIntent, private void startIntentLegacy(PendingIntent intent, Intent fillInIntent, @SplitPosition int position, @Nullable Bundle options) { boolean startSameActivityAdjacently = isLaunchingAdjacently(intent.getIntent(), position); final WindowContainerTransaction evictWct = new WindowContainerTransaction(); mStageCoordinator.prepareEvictChildTasks(position, evictWct); Loading @@ -397,8 +387,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, IRemoteAnimationFinishedCallback finishedCallback, SurfaceControl.Transaction t) { if (apps == null || apps.length == 0) { if (startSameActivityAdjacently) { // Switch split position if dragging the same activity to another side. // Switch the split position if launching as MULTIPLE_TASK failed. if ((fillInIntent.getFlags() & FLAG_ACTIVITY_MULTIPLE_TASK) != 0) { setSideStagePosition(SplitLayout.reversePosition( mStageCoordinator.getSideStagePosition())); } Loading Loading @@ -432,18 +422,6 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, final WindowContainerTransaction wct = new WindowContainerTransaction(); options = mStageCoordinator.resolveStartStage(STAGE_TYPE_UNDEFINED, position, options, wct); // Flag this as a no-user-action launch to prevent sending user leaving event to the current // top activity since it's going to be put into another side of the split. This prevents the // current top activity from going into pip mode due to user leaving event. if (fillInIntent == null) { fillInIntent = new Intent(); } fillInIntent.addFlags(FLAG_ACTIVITY_NO_USER_ACTION); if (startSameActivityAdjacently) { fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK); ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK"); } wct.sendPendingIntent(intent, fillInIntent, options); mSyncQueue.queue(transition, WindowManager.TRANSIT_OPEN, wct); } Loading libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java +108 −33 Original line number Diff line number Diff line Loading @@ -62,13 +62,12 @@ class SplitScreenTransitions { private final Runnable mOnFinish; DismissTransition mPendingDismiss = null; IBinder mPendingEnter = null; IBinder mPendingRecent = null; TransitSession mPendingEnter = null; TransitSession mPendingRecent = null; private IBinder mAnimatingTransition = null; OneShotRemoteHandler mPendingRemoteHandler = null; private OneShotRemoteHandler mActiveRemoteHandler = null; private boolean mEnterTransitionMerged; private final Transitions.TransitionFinishCallback mRemoteFinishCB = this::onFinish; Loading Loading @@ -145,7 +144,7 @@ class SplitScreenTransitions { continue; } if (transition == mPendingEnter && (mainRoot.equals(change.getContainer()) if (isPendingEnter(transition) && (mainRoot.equals(change.getContainer()) || sideRoot.equals(change.getContainer()))) { t.setPosition(leash, change.getEndAbsBounds().left, change.getEndAbsBounds().top); t.setWindowCrop(leash, change.getEndAbsBounds().width(), Loading @@ -171,12 +170,40 @@ class SplitScreenTransitions { onFinish(null /* wct */, null /* wctCB */); } boolean isPendingTransition(IBinder transition) { return isPendingEnter(transition) || isPendingDismiss(transition) || isPendingRecent(transition); } boolean isPendingEnter(IBinder transition) { return mPendingEnter != null && mPendingEnter.mTransition == transition; } boolean isPendingRecent(IBinder transition) { return mPendingRecent != null && mPendingRecent.mTransition == transition; } boolean isPendingDismiss(IBinder transition) { return mPendingDismiss != null && mPendingDismiss.mTransition == transition; } /** Starts a transition to enter split with a remote transition animator. */ IBinder startEnterTransition(@WindowManager.TransitionType int transitType, @NonNull WindowContainerTransaction wct, @Nullable RemoteTransition remoteTransition, @NonNull Transitions.TransitionHandler handler) { IBinder startEnterTransition( @WindowManager.TransitionType int transitType, WindowContainerTransaction wct, @Nullable RemoteTransition remoteTransition, Transitions.TransitionHandler handler, @Nullable TransitionCallback callback) { final IBinder transition = mTransitions.startTransition(transitType, wct, handler); mPendingEnter = transition; setEnterTransition(transition, remoteTransition, callback); return transition; } /** Sets a transition to enter split. */ void setEnterTransition(@NonNull IBinder transition, @Nullable RemoteTransition remoteTransition, @Nullable TransitionCallback callback) { mPendingEnter = new TransitSession(transition, callback); if (remoteTransition != null) { // Wrapping it for ease-of-use (OneShot handles all the binder linking/death stuff) Loading @@ -184,7 +211,9 @@ class SplitScreenTransitions { mTransitions.getMainExecutor(), remoteTransition); mPendingRemoteHandler.setTransition(transition); } return transition; ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " splitTransition " + " deduced Enter split screen"); } /** Starts a transition to dismiss split. */ Loading @@ -209,8 +238,8 @@ class SplitScreenTransitions { } void setRecentTransition(@NonNull IBinder transition, @Nullable RemoteTransition remoteTransition) { mPendingRecent = transition; @Nullable RemoteTransition remoteTransition, @Nullable TransitionCallback callback) { mPendingRecent = new TransitSession(transition, callback); if (remoteTransition != null) { // Wrapping it for ease-of-use (OneShot handles all the binder linking/death stuff) Loading @@ -226,6 +255,18 @@ class SplitScreenTransitions { void mergeAnimation(IBinder transition, TransitionInfo info, SurfaceControl.Transaction t, IBinder mergeTarget, Transitions.TransitionFinishCallback finishCallback) { if (mergeTarget != mAnimatingTransition) return; if (isPendingEnter(transition) && isPendingRecent(mergeTarget)) { mPendingRecent.mCallback = new TransitionCallback() { @Override public void onTransitionFinished(WindowContainerTransaction finishWct, SurfaceControl.Transaction finishT) { // Since there's an entering transition merged, recent transition no longer // need to handle entering split screen after the transition finished. } }; } if (mActiveRemoteHandler != null) { mActiveRemoteHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback); } else { Loading @@ -247,38 +288,55 @@ class SplitScreenTransitions { } void onTransitionConsumed(@NonNull IBinder transition, boolean aborted) { if (aborted) return; // Once a pending enter transition got merged, make sure to append the reset of finishing // operations to the finish transition. if (transition == mPendingEnter) { if (isPendingEnter(transition)) { if (!aborted) { // An enter transition got merged, appends the rest operations to finish entering // split screen. // TODO (b/238856352): Passed-in the proper finish transition to merge instead. if (mFinishTransaction == null) { mFinishTransaction = mTransactionPool.acquire(); } mStageCoordinator.finishEnterSplitScreen(mFinishTransaction); } mPendingEnter.mCallback.onTransitionConsumed(aborted); mPendingEnter = null; mPendingRemoteHandler = null; mEnterTransitionMerged = true; } else if (isPendingDismiss(transition)) { mPendingDismiss.mCallback.onTransitionConsumed(aborted); mPendingDismiss = null; } else if (isPendingRecent(transition)) { mPendingRecent.mCallback.onTransitionConsumed(aborted); mPendingRecent = null; mPendingRemoteHandler = null; } } void onFinish(WindowContainerTransaction wct, WindowContainerTransactionCallback wctCB) { if (!mAnimations.isEmpty()) return; if (mAnimatingTransition == mPendingEnter) { TransitionCallback callback = null; if (isPendingEnter(mAnimatingTransition)) { callback = mPendingEnter.mCallback; mPendingEnter = null; } if (mPendingDismiss != null && mPendingDismiss.mTransition == mAnimatingTransition) { if (isPendingDismiss(mAnimatingTransition)) { callback = mPendingDismiss.mCallback; mPendingDismiss = null; } if (mAnimatingTransition == mPendingRecent) { if (!mEnterTransitionMerged) { if (wct == null) wct = new WindowContainerTransaction(); mStageCoordinator.onRecentTransitionFinished(wct, mFinishTransaction); } if (isPendingRecent(mAnimatingTransition)) { callback = mPendingRecent.mCallback; mPendingRecent = null; } if (callback != null) { if (wct == null) wct = new WindowContainerTransaction(); callback.onTransitionFinished(wct, mFinishTransaction); } mPendingRemoteHandler = null; mActiveRemoteHandler = null; mAnimatingTransition = null; mEnterTransitionMerged = false; mOnFinish.run(); if (mFinishTransaction != null) { Loading Loading @@ -382,17 +440,34 @@ class SplitScreenTransitions { || info.getType() == TRANSIT_SPLIT_SCREEN_PAIR_OPEN; } /** Bundled information of dismiss transition. */ static class DismissTransition { IBinder mTransition; /** Clean-up callbacks for transition. */ interface TransitionCallback { /** Calls when the transition got consumed. */ default void onTransitionConsumed(boolean aborted) {} /** Calls when the transition finished. */ default void onTransitionFinished(WindowContainerTransaction finishWct, SurfaceControl.Transaction finishT) {} } int mReason; /** Session for a transition and its clean-up callback. */ static class TransitSession { final IBinder mTransition; TransitionCallback mCallback; @SplitScreen.StageType int mDismissTop; TransitSession(IBinder transition, @Nullable TransitionCallback callback) { mTransition = transition; mCallback = callback != null ? callback : new TransitionCallback() {}; } } /** Bundled information of dismiss transition. */ static class DismissTransition extends TransitSession { final int mReason; final @SplitScreen.StageType int mDismissTop; DismissTransition(IBinder transition, int reason, int dismissTop) { this.mTransition = transition; super(transition, null /* callback */); this.mReason = reason; this.mDismissTop = dismissTop; } Loading libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +92 −45 Original line number Diff line number Diff line Loading @@ -24,6 +24,7 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER; import static android.view.WindowManager.TRANSIT_CHANGE; Loading Loading @@ -214,6 +215,33 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } }; private final SplitScreenTransitions.TransitionCallback mRecentTransitionCallback = new SplitScreenTransitions.TransitionCallback() { @Override public void onTransitionFinished(WindowContainerTransaction finishWct, SurfaceControl.Transaction finishT) { // Check if the recent transition is finished by returning to the current split, so we // can restore the divider bar. for (int i = 0; i < finishWct.getHierarchyOps().size(); ++i) { final WindowContainerTransaction.HierarchyOp op = finishWct.getHierarchyOps().get(i); final IBinder container = op.getContainer(); if (op.getType() == HIERARCHY_OP_TYPE_REORDER && op.getToTop() && (mMainStage.containsContainer(container) || mSideStage.containsContainer(container))) { setDividerVisibility(true, finishT); return; } } // Dismiss the split screen if it's not returning to split. prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, finishWct); setSplitsVisible(false); setDividerVisibility(false, finishT); logExit(EXIT_REASON_UNKNOWN); } }; StageCoordinator(Context context, int displayId, SyncTransactionQueue syncQueue, ShellTaskOrganizer taskOrganizer, DisplayController displayController, DisplayImeController displayImeController, Loading Loading @@ -337,15 +365,23 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, final WindowContainerTransaction evictWct = new WindowContainerTransaction(); targetStage.evictAllChildren(evictWct); targetStage.addTask(task, wct); if (!evictWct.isEmpty()) { wct.merge(evictWct, true /* transfer */); } if (ENABLE_SHELL_TRANSITIONS) { prepareEnterSplitScreen(wct); mSplitTransitions.startEnterTransition(TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE, wct, null, this); mSplitTransitions.startEnterTransition(TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE, wct, null, this, new SplitScreenTransitions.TransitionCallback() { @Override public void onTransitionFinished(WindowContainerTransaction finishWct, SurfaceControl.Transaction finishT) { if (!evictWct.isEmpty()) { finishWct.merge(evictWct, true); } } }); } else { if (!evictWct.isEmpty()) { wct.merge(evictWct, true /* transfer */); } mTaskOrganizer.applyTransaction(wct); } return true; Loading @@ -365,6 +401,39 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, return result; } /** Launches an activity into split. */ void startIntent(PendingIntent intent, Intent fillInIntent, @SplitPosition int position, @Nullable Bundle options) { final WindowContainerTransaction wct = new WindowContainerTransaction(); final WindowContainerTransaction evictWct = new WindowContainerTransaction(); prepareEvictChildTasks(position, evictWct); options = resolveStartStage(STAGE_TYPE_UNDEFINED, position, options, null /* wct */); wct.sendPendingIntent(intent, fillInIntent, options); prepareEnterSplitScreen(wct, null /* taskInfo */, position); mSplitTransitions.startEnterTransition(TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE, wct, null, this, new SplitScreenTransitions.TransitionCallback() { @Override public void onTransitionConsumed(boolean aborted) { // Switch the split position if launching as MULTIPLE_TASK failed. if (aborted && (fillInIntent.getFlags() & FLAG_ACTIVITY_MULTIPLE_TASK) != 0) { setSideStagePositionAnimated( SplitLayout.reversePosition(mSideStagePosition)); } } @Override public void onTransitionFinished(WindowContainerTransaction finishWct, SurfaceControl.Transaction finishT) { if (!evictWct.isEmpty()) { finishWct.merge(evictWct, true); } } }); } /** Starts 2 tasks in one transition. */ void startTasks(int mainTaskId, @Nullable Bundle mainOptions, int sideTaskId, @Nullable Bundle sideOptions, @SplitPosition int sidePosition, float splitRatio, Loading Loading @@ -395,7 +464,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, wct.startTask(sideTaskId, sideOptions); mSplitTransitions.startEnterTransition( TRANSIT_SPLIT_SCREEN_PAIR_OPEN, wct, remoteTransition, this); TRANSIT_SPLIT_SCREEN_PAIR_OPEN, wct, remoteTransition, this, null); } /** Starts 2 tasks in one legacy transition. */ Loading Loading @@ -617,11 +686,13 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } int getTaskId(@SplitPosition int splitPosition) { if (mSideStagePosition == splitPosition) { return mSideStage.getTopVisibleChildTaskId(); } else { return mMainStage.getTopVisibleChildTaskId(); if (splitPosition == SPLIT_POSITION_UNDEFINED) { return INVALID_TASK_ID; } return mSideStagePosition == splitPosition ? mSideStage.getTopVisibleChildTaskId() : mMainStage.getTopVisibleChildTaskId(); } void setSideStagePositionAnimated(@SplitPosition int sideStagePosition) { Loading Loading @@ -861,6 +932,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mSplitLayout.init(); setDividerVisibility(true, t); updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */); t.show(mRootTaskLeash); setSplitsVisible(true); mShouldUpdateRecents = true; updateRecentTasksSplitPair(); Loading Loading @@ -1543,14 +1615,14 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } else if (activityType == ACTIVITY_TYPE_HOME || activityType == ACTIVITY_TYPE_RECENTS) { // Enter overview panel, so start recent transition. mSplitTransitions.setRecentTransition(transition, request.getRemoteTransition()); mSplitTransitions.setRecentTransition(transition, request.getRemoteTransition(), mRecentTransitionCallback); } else if (mSplitTransitions.mPendingRecent == null) { // If split-task is not controlled by recents animation // and occluded by the other fullscreen task, dismiss both. prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, out); mSplitTransitions.setDismissTransition(transition, STAGE_TYPE_UNDEFINED, EXIT_REASON_UNKNOWN); mSplitTransitions.setDismissTransition( transition, STAGE_TYPE_UNDEFINED, EXIT_REASON_UNKNOWN); } } } else { Loading @@ -1558,7 +1630,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, // One task is appearing into split, prepare to enter split screen. out = new WindowContainerTransaction(); prepareEnterSplitScreen(out); mSplitTransitions.mPendingEnter = transition; mSplitTransitions.setEnterTransition( transition, request.getRemoteTransition(), null /* callback */); } } return out; Loading Loading @@ -1614,10 +1687,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback) { if (transition != mSplitTransitions.mPendingEnter && transition != mSplitTransitions.mPendingRecent && (mSplitTransitions.mPendingDismiss == null || mSplitTransitions.mPendingDismiss.mTransition != transition)) { if (!mSplitTransitions.isPendingTransition(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 @@ -1664,12 +1734,11 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } boolean shouldAnimate = true; if (mSplitTransitions.mPendingEnter == transition) { if (mSplitTransitions.isPendingEnter(transition)) { shouldAnimate = startPendingEnterAnimation(transition, info, startTransaction); } else if (mSplitTransitions.mPendingRecent == transition) { } else if (mSplitTransitions.isPendingRecent(transition)) { shouldAnimate = startPendingRecentAnimation(transition, info, startTransaction); } else if (mSplitTransitions.mPendingDismiss != null && mSplitTransitions.mPendingDismiss.mTransition == transition) { } else if (mSplitTransitions.isPendingDismiss(transition)) { shouldAnimate = startPendingDismissAnimation( mSplitTransitions.mPendingDismiss, info, startTransaction, finishTransaction); } Loading Loading @@ -1837,28 +1906,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, return true; } void onRecentTransitionFinished(WindowContainerTransaction wct, SurfaceControl.Transaction finishT) { // Check if the recent transition is finished by returning to the current split so we can // restore the divider bar. for (int i = 0; i < wct.getHierarchyOps().size(); ++i) { final WindowContainerTransaction.HierarchyOp op = wct.getHierarchyOps().get(i); final IBinder container = op.getContainer(); if (op.getType() == HIERARCHY_OP_TYPE_REORDER && op.getToTop() && (mMainStage.containsContainer(container) || mSideStage.containsContainer(container))) { setDividerVisibility(true, finishT); return; } } // Dismiss the split screen is it's not returning to split. prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, wct); setSplitsVisible(false); setDividerVisibility(false, finishT); logExit(EXIT_REASON_UNKNOWN); } private void addDividerBarToTransition(@NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t, boolean show) { final SurfaceControl leash = mSplitLayout.getDividerLeash(); Loading libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java +2 −2 Original line number Diff line number Diff line Loading @@ -182,7 +182,7 @@ public class SplitTransitionTests extends ShellTestCase { IBinder transition = mSplitScreenTransitions.startEnterTransition( TRANSIT_SPLIT_SCREEN_PAIR_OPEN, new WindowContainerTransaction(), new RemoteTransition(testRemote), mStageCoordinator); new RemoteTransition(testRemote), mStageCoordinator, null); mMainStage.onTaskAppeared(mMainChild, createMockSurface()); mSideStage.onTaskAppeared(mSideChild, createMockSurface()); boolean accepted = mStageCoordinator.startAnimation(transition, info, Loading Loading @@ -422,7 +422,7 @@ public class SplitTransitionTests extends ShellTestCase { TransitionInfo enterInfo = createEnterPairInfo(); IBinder enterTransit = mSplitScreenTransitions.startEnterTransition( TRANSIT_SPLIT_SCREEN_PAIR_OPEN, new WindowContainerTransaction(), new RemoteTransition(new TestRemoteTransition()), mStageCoordinator); new RemoteTransition(new TestRemoteTransition()), mStageCoordinator, null); mMainStage.onTaskAppeared(mMainChild, createMockSurface()); mSideStage.onTaskAppeared(mSideChild, createMockSurface()); mStageCoordinator.startAnimation(enterTransit, enterInfo, Loading Loading
libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java +19 −41 Original line number Diff line number Diff line Loading @@ -352,15 +352,6 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, public void startIntent(PendingIntent intent, @Nullable Intent fillInIntent, @SplitPosition int position, @Nullable Bundle options) { if (!ENABLE_SHELL_TRANSITIONS) { startIntentLegacy(intent, fillInIntent, position, options); return; } try { options = mStageCoordinator.resolveStartStage(STAGE_TYPE_UNDEFINED, position, options, null /* wct */); if (fillInIntent == null) { fillInIntent = new Intent(); } Loading @@ -376,17 +367,16 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK"); } intent.send(mContext, 0, fillInIntent, null /* onFinished */, null /* handler */, null /* requiredPermission */, options); } catch (PendingIntent.CanceledException e) { Slog.e(TAG, "Failed to launch task", e); if (!ENABLE_SHELL_TRANSITIONS) { startIntentLegacy(intent, fillInIntent, position, options); return; } mStageCoordinator.startIntent(intent, fillInIntent, position, options); } private void startIntentLegacy(PendingIntent intent, @Nullable Intent fillInIntent, private void startIntentLegacy(PendingIntent intent, Intent fillInIntent, @SplitPosition int position, @Nullable Bundle options) { boolean startSameActivityAdjacently = isLaunchingAdjacently(intent.getIntent(), position); final WindowContainerTransaction evictWct = new WindowContainerTransaction(); mStageCoordinator.prepareEvictChildTasks(position, evictWct); Loading @@ -397,8 +387,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, IRemoteAnimationFinishedCallback finishedCallback, SurfaceControl.Transaction t) { if (apps == null || apps.length == 0) { if (startSameActivityAdjacently) { // Switch split position if dragging the same activity to another side. // Switch the split position if launching as MULTIPLE_TASK failed. if ((fillInIntent.getFlags() & FLAG_ACTIVITY_MULTIPLE_TASK) != 0) { setSideStagePosition(SplitLayout.reversePosition( mStageCoordinator.getSideStagePosition())); } Loading Loading @@ -432,18 +422,6 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, final WindowContainerTransaction wct = new WindowContainerTransaction(); options = mStageCoordinator.resolveStartStage(STAGE_TYPE_UNDEFINED, position, options, wct); // Flag this as a no-user-action launch to prevent sending user leaving event to the current // top activity since it's going to be put into another side of the split. This prevents the // current top activity from going into pip mode due to user leaving event. if (fillInIntent == null) { fillInIntent = new Intent(); } fillInIntent.addFlags(FLAG_ACTIVITY_NO_USER_ACTION); if (startSameActivityAdjacently) { fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK); ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK"); } wct.sendPendingIntent(intent, fillInIntent, options); mSyncQueue.queue(transition, WindowManager.TRANSIT_OPEN, wct); } Loading
libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java +108 −33 Original line number Diff line number Diff line Loading @@ -62,13 +62,12 @@ class SplitScreenTransitions { private final Runnable mOnFinish; DismissTransition mPendingDismiss = null; IBinder mPendingEnter = null; IBinder mPendingRecent = null; TransitSession mPendingEnter = null; TransitSession mPendingRecent = null; private IBinder mAnimatingTransition = null; OneShotRemoteHandler mPendingRemoteHandler = null; private OneShotRemoteHandler mActiveRemoteHandler = null; private boolean mEnterTransitionMerged; private final Transitions.TransitionFinishCallback mRemoteFinishCB = this::onFinish; Loading Loading @@ -145,7 +144,7 @@ class SplitScreenTransitions { continue; } if (transition == mPendingEnter && (mainRoot.equals(change.getContainer()) if (isPendingEnter(transition) && (mainRoot.equals(change.getContainer()) || sideRoot.equals(change.getContainer()))) { t.setPosition(leash, change.getEndAbsBounds().left, change.getEndAbsBounds().top); t.setWindowCrop(leash, change.getEndAbsBounds().width(), Loading @@ -171,12 +170,40 @@ class SplitScreenTransitions { onFinish(null /* wct */, null /* wctCB */); } boolean isPendingTransition(IBinder transition) { return isPendingEnter(transition) || isPendingDismiss(transition) || isPendingRecent(transition); } boolean isPendingEnter(IBinder transition) { return mPendingEnter != null && mPendingEnter.mTransition == transition; } boolean isPendingRecent(IBinder transition) { return mPendingRecent != null && mPendingRecent.mTransition == transition; } boolean isPendingDismiss(IBinder transition) { return mPendingDismiss != null && mPendingDismiss.mTransition == transition; } /** Starts a transition to enter split with a remote transition animator. */ IBinder startEnterTransition(@WindowManager.TransitionType int transitType, @NonNull WindowContainerTransaction wct, @Nullable RemoteTransition remoteTransition, @NonNull Transitions.TransitionHandler handler) { IBinder startEnterTransition( @WindowManager.TransitionType int transitType, WindowContainerTransaction wct, @Nullable RemoteTransition remoteTransition, Transitions.TransitionHandler handler, @Nullable TransitionCallback callback) { final IBinder transition = mTransitions.startTransition(transitType, wct, handler); mPendingEnter = transition; setEnterTransition(transition, remoteTransition, callback); return transition; } /** Sets a transition to enter split. */ void setEnterTransition(@NonNull IBinder transition, @Nullable RemoteTransition remoteTransition, @Nullable TransitionCallback callback) { mPendingEnter = new TransitSession(transition, callback); if (remoteTransition != null) { // Wrapping it for ease-of-use (OneShot handles all the binder linking/death stuff) Loading @@ -184,7 +211,9 @@ class SplitScreenTransitions { mTransitions.getMainExecutor(), remoteTransition); mPendingRemoteHandler.setTransition(transition); } return transition; ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " splitTransition " + " deduced Enter split screen"); } /** Starts a transition to dismiss split. */ Loading @@ -209,8 +238,8 @@ class SplitScreenTransitions { } void setRecentTransition(@NonNull IBinder transition, @Nullable RemoteTransition remoteTransition) { mPendingRecent = transition; @Nullable RemoteTransition remoteTransition, @Nullable TransitionCallback callback) { mPendingRecent = new TransitSession(transition, callback); if (remoteTransition != null) { // Wrapping it for ease-of-use (OneShot handles all the binder linking/death stuff) Loading @@ -226,6 +255,18 @@ class SplitScreenTransitions { void mergeAnimation(IBinder transition, TransitionInfo info, SurfaceControl.Transaction t, IBinder mergeTarget, Transitions.TransitionFinishCallback finishCallback) { if (mergeTarget != mAnimatingTransition) return; if (isPendingEnter(transition) && isPendingRecent(mergeTarget)) { mPendingRecent.mCallback = new TransitionCallback() { @Override public void onTransitionFinished(WindowContainerTransaction finishWct, SurfaceControl.Transaction finishT) { // Since there's an entering transition merged, recent transition no longer // need to handle entering split screen after the transition finished. } }; } if (mActiveRemoteHandler != null) { mActiveRemoteHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback); } else { Loading @@ -247,38 +288,55 @@ class SplitScreenTransitions { } void onTransitionConsumed(@NonNull IBinder transition, boolean aborted) { if (aborted) return; // Once a pending enter transition got merged, make sure to append the reset of finishing // operations to the finish transition. if (transition == mPendingEnter) { if (isPendingEnter(transition)) { if (!aborted) { // An enter transition got merged, appends the rest operations to finish entering // split screen. // TODO (b/238856352): Passed-in the proper finish transition to merge instead. if (mFinishTransaction == null) { mFinishTransaction = mTransactionPool.acquire(); } mStageCoordinator.finishEnterSplitScreen(mFinishTransaction); } mPendingEnter.mCallback.onTransitionConsumed(aborted); mPendingEnter = null; mPendingRemoteHandler = null; mEnterTransitionMerged = true; } else if (isPendingDismiss(transition)) { mPendingDismiss.mCallback.onTransitionConsumed(aborted); mPendingDismiss = null; } else if (isPendingRecent(transition)) { mPendingRecent.mCallback.onTransitionConsumed(aborted); mPendingRecent = null; mPendingRemoteHandler = null; } } void onFinish(WindowContainerTransaction wct, WindowContainerTransactionCallback wctCB) { if (!mAnimations.isEmpty()) return; if (mAnimatingTransition == mPendingEnter) { TransitionCallback callback = null; if (isPendingEnter(mAnimatingTransition)) { callback = mPendingEnter.mCallback; mPendingEnter = null; } if (mPendingDismiss != null && mPendingDismiss.mTransition == mAnimatingTransition) { if (isPendingDismiss(mAnimatingTransition)) { callback = mPendingDismiss.mCallback; mPendingDismiss = null; } if (mAnimatingTransition == mPendingRecent) { if (!mEnterTransitionMerged) { if (wct == null) wct = new WindowContainerTransaction(); mStageCoordinator.onRecentTransitionFinished(wct, mFinishTransaction); } if (isPendingRecent(mAnimatingTransition)) { callback = mPendingRecent.mCallback; mPendingRecent = null; } if (callback != null) { if (wct == null) wct = new WindowContainerTransaction(); callback.onTransitionFinished(wct, mFinishTransaction); } mPendingRemoteHandler = null; mActiveRemoteHandler = null; mAnimatingTransition = null; mEnterTransitionMerged = false; mOnFinish.run(); if (mFinishTransaction != null) { Loading Loading @@ -382,17 +440,34 @@ class SplitScreenTransitions { || info.getType() == TRANSIT_SPLIT_SCREEN_PAIR_OPEN; } /** Bundled information of dismiss transition. */ static class DismissTransition { IBinder mTransition; /** Clean-up callbacks for transition. */ interface TransitionCallback { /** Calls when the transition got consumed. */ default void onTransitionConsumed(boolean aborted) {} /** Calls when the transition finished. */ default void onTransitionFinished(WindowContainerTransaction finishWct, SurfaceControl.Transaction finishT) {} } int mReason; /** Session for a transition and its clean-up callback. */ static class TransitSession { final IBinder mTransition; TransitionCallback mCallback; @SplitScreen.StageType int mDismissTop; TransitSession(IBinder transition, @Nullable TransitionCallback callback) { mTransition = transition; mCallback = callback != null ? callback : new TransitionCallback() {}; } } /** Bundled information of dismiss transition. */ static class DismissTransition extends TransitSession { final int mReason; final @SplitScreen.StageType int mDismissTop; DismissTransition(IBinder transition, int reason, int dismissTop) { this.mTransition = transition; super(transition, null /* callback */); this.mReason = reason; this.mDismissTop = dismissTop; } Loading
libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +92 −45 Original line number Diff line number Diff line Loading @@ -24,6 +24,7 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER; import static android.view.WindowManager.TRANSIT_CHANGE; Loading Loading @@ -214,6 +215,33 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } }; private final SplitScreenTransitions.TransitionCallback mRecentTransitionCallback = new SplitScreenTransitions.TransitionCallback() { @Override public void onTransitionFinished(WindowContainerTransaction finishWct, SurfaceControl.Transaction finishT) { // Check if the recent transition is finished by returning to the current split, so we // can restore the divider bar. for (int i = 0; i < finishWct.getHierarchyOps().size(); ++i) { final WindowContainerTransaction.HierarchyOp op = finishWct.getHierarchyOps().get(i); final IBinder container = op.getContainer(); if (op.getType() == HIERARCHY_OP_TYPE_REORDER && op.getToTop() && (mMainStage.containsContainer(container) || mSideStage.containsContainer(container))) { setDividerVisibility(true, finishT); return; } } // Dismiss the split screen if it's not returning to split. prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, finishWct); setSplitsVisible(false); setDividerVisibility(false, finishT); logExit(EXIT_REASON_UNKNOWN); } }; StageCoordinator(Context context, int displayId, SyncTransactionQueue syncQueue, ShellTaskOrganizer taskOrganizer, DisplayController displayController, DisplayImeController displayImeController, Loading Loading @@ -337,15 +365,23 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, final WindowContainerTransaction evictWct = new WindowContainerTransaction(); targetStage.evictAllChildren(evictWct); targetStage.addTask(task, wct); if (!evictWct.isEmpty()) { wct.merge(evictWct, true /* transfer */); } if (ENABLE_SHELL_TRANSITIONS) { prepareEnterSplitScreen(wct); mSplitTransitions.startEnterTransition(TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE, wct, null, this); mSplitTransitions.startEnterTransition(TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE, wct, null, this, new SplitScreenTransitions.TransitionCallback() { @Override public void onTransitionFinished(WindowContainerTransaction finishWct, SurfaceControl.Transaction finishT) { if (!evictWct.isEmpty()) { finishWct.merge(evictWct, true); } } }); } else { if (!evictWct.isEmpty()) { wct.merge(evictWct, true /* transfer */); } mTaskOrganizer.applyTransaction(wct); } return true; Loading @@ -365,6 +401,39 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, return result; } /** Launches an activity into split. */ void startIntent(PendingIntent intent, Intent fillInIntent, @SplitPosition int position, @Nullable Bundle options) { final WindowContainerTransaction wct = new WindowContainerTransaction(); final WindowContainerTransaction evictWct = new WindowContainerTransaction(); prepareEvictChildTasks(position, evictWct); options = resolveStartStage(STAGE_TYPE_UNDEFINED, position, options, null /* wct */); wct.sendPendingIntent(intent, fillInIntent, options); prepareEnterSplitScreen(wct, null /* taskInfo */, position); mSplitTransitions.startEnterTransition(TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE, wct, null, this, new SplitScreenTransitions.TransitionCallback() { @Override public void onTransitionConsumed(boolean aborted) { // Switch the split position if launching as MULTIPLE_TASK failed. if (aborted && (fillInIntent.getFlags() & FLAG_ACTIVITY_MULTIPLE_TASK) != 0) { setSideStagePositionAnimated( SplitLayout.reversePosition(mSideStagePosition)); } } @Override public void onTransitionFinished(WindowContainerTransaction finishWct, SurfaceControl.Transaction finishT) { if (!evictWct.isEmpty()) { finishWct.merge(evictWct, true); } } }); } /** Starts 2 tasks in one transition. */ void startTasks(int mainTaskId, @Nullable Bundle mainOptions, int sideTaskId, @Nullable Bundle sideOptions, @SplitPosition int sidePosition, float splitRatio, Loading Loading @@ -395,7 +464,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, wct.startTask(sideTaskId, sideOptions); mSplitTransitions.startEnterTransition( TRANSIT_SPLIT_SCREEN_PAIR_OPEN, wct, remoteTransition, this); TRANSIT_SPLIT_SCREEN_PAIR_OPEN, wct, remoteTransition, this, null); } /** Starts 2 tasks in one legacy transition. */ Loading Loading @@ -617,11 +686,13 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } int getTaskId(@SplitPosition int splitPosition) { if (mSideStagePosition == splitPosition) { return mSideStage.getTopVisibleChildTaskId(); } else { return mMainStage.getTopVisibleChildTaskId(); if (splitPosition == SPLIT_POSITION_UNDEFINED) { return INVALID_TASK_ID; } return mSideStagePosition == splitPosition ? mSideStage.getTopVisibleChildTaskId() : mMainStage.getTopVisibleChildTaskId(); } void setSideStagePositionAnimated(@SplitPosition int sideStagePosition) { Loading Loading @@ -861,6 +932,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mSplitLayout.init(); setDividerVisibility(true, t); updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */); t.show(mRootTaskLeash); setSplitsVisible(true); mShouldUpdateRecents = true; updateRecentTasksSplitPair(); Loading Loading @@ -1543,14 +1615,14 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } else if (activityType == ACTIVITY_TYPE_HOME || activityType == ACTIVITY_TYPE_RECENTS) { // Enter overview panel, so start recent transition. mSplitTransitions.setRecentTransition(transition, request.getRemoteTransition()); mSplitTransitions.setRecentTransition(transition, request.getRemoteTransition(), mRecentTransitionCallback); } else if (mSplitTransitions.mPendingRecent == null) { // If split-task is not controlled by recents animation // and occluded by the other fullscreen task, dismiss both. prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, out); mSplitTransitions.setDismissTransition(transition, STAGE_TYPE_UNDEFINED, EXIT_REASON_UNKNOWN); mSplitTransitions.setDismissTransition( transition, STAGE_TYPE_UNDEFINED, EXIT_REASON_UNKNOWN); } } } else { Loading @@ -1558,7 +1630,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, // One task is appearing into split, prepare to enter split screen. out = new WindowContainerTransaction(); prepareEnterSplitScreen(out); mSplitTransitions.mPendingEnter = transition; mSplitTransitions.setEnterTransition( transition, request.getRemoteTransition(), null /* callback */); } } return out; Loading Loading @@ -1614,10 +1687,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback) { if (transition != mSplitTransitions.mPendingEnter && transition != mSplitTransitions.mPendingRecent && (mSplitTransitions.mPendingDismiss == null || mSplitTransitions.mPendingDismiss.mTransition != transition)) { if (!mSplitTransitions.isPendingTransition(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 @@ -1664,12 +1734,11 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } boolean shouldAnimate = true; if (mSplitTransitions.mPendingEnter == transition) { if (mSplitTransitions.isPendingEnter(transition)) { shouldAnimate = startPendingEnterAnimation(transition, info, startTransaction); } else if (mSplitTransitions.mPendingRecent == transition) { } else if (mSplitTransitions.isPendingRecent(transition)) { shouldAnimate = startPendingRecentAnimation(transition, info, startTransaction); } else if (mSplitTransitions.mPendingDismiss != null && mSplitTransitions.mPendingDismiss.mTransition == transition) { } else if (mSplitTransitions.isPendingDismiss(transition)) { shouldAnimate = startPendingDismissAnimation( mSplitTransitions.mPendingDismiss, info, startTransaction, finishTransaction); } Loading Loading @@ -1837,28 +1906,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, return true; } void onRecentTransitionFinished(WindowContainerTransaction wct, SurfaceControl.Transaction finishT) { // Check if the recent transition is finished by returning to the current split so we can // restore the divider bar. for (int i = 0; i < wct.getHierarchyOps().size(); ++i) { final WindowContainerTransaction.HierarchyOp op = wct.getHierarchyOps().get(i); final IBinder container = op.getContainer(); if (op.getType() == HIERARCHY_OP_TYPE_REORDER && op.getToTop() && (mMainStage.containsContainer(container) || mSideStage.containsContainer(container))) { setDividerVisibility(true, finishT); return; } } // Dismiss the split screen is it's not returning to split. prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, wct); setSplitsVisible(false); setDividerVisibility(false, finishT); logExit(EXIT_REASON_UNKNOWN); } private void addDividerBarToTransition(@NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t, boolean show) { final SurfaceControl leash = mSplitLayout.getDividerLeash(); Loading
libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java +2 −2 Original line number Diff line number Diff line Loading @@ -182,7 +182,7 @@ public class SplitTransitionTests extends ShellTestCase { IBinder transition = mSplitScreenTransitions.startEnterTransition( TRANSIT_SPLIT_SCREEN_PAIR_OPEN, new WindowContainerTransaction(), new RemoteTransition(testRemote), mStageCoordinator); new RemoteTransition(testRemote), mStageCoordinator, null); mMainStage.onTaskAppeared(mMainChild, createMockSurface()); mSideStage.onTaskAppeared(mSideChild, createMockSurface()); boolean accepted = mStageCoordinator.startAnimation(transition, info, Loading Loading @@ -422,7 +422,7 @@ public class SplitTransitionTests extends ShellTestCase { TransitionInfo enterInfo = createEnterPairInfo(); IBinder enterTransit = mSplitScreenTransitions.startEnterTransition( TRANSIT_SPLIT_SCREEN_PAIR_OPEN, new WindowContainerTransaction(), new RemoteTransition(new TestRemoteTransition()), mStageCoordinator); new RemoteTransition(new TestRemoteTransition()), mStageCoordinator, null); mMainStage.onTaskAppeared(mMainChild, createMockSurface()); mSideStage.onTaskAppeared(mSideChild, createMockSurface()); mStageCoordinator.startAnimation(enterTransit, enterInfo, Loading