Consolidate drag to split gesture with shell transition

Refine SplitScreenTransitions with TransitionCallback to consolidate
transition handling logic.

1. Support to replace previous pairing app with drag-to-split gesture.
2. Support to switch split position by drag and drop a single-instance
   app to another side of the split.

Fix: 236814427
Fix: 236814471
Test: atest WMShellUnitTests
Test: enable shell transition, enter split screen, verified
      1. drag and drop gesture can replace apps in split
      2. drag and drop single-instance app to another side switch
	 split position
      3. quick switch and overview switch works as expected

Change-Id: Icde96369b79947e93a53ac589e64201aa32cf337
This commit is contained in:
Jerry Chang
2022-06-29 12:06:19 +00:00
parent 6a6b2bb5d0
commit 57c6a31125
4 changed files with 222 additions and 122 deletions

View File

@@ -352,41 +352,31 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
public void startIntent(PendingIntent intent, @Nullable Intent fillInIntent,
@SplitPosition int position, @Nullable Bundle options) {
if (fillInIntent == null) {
fillInIntent = new Intent();
}
// 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.
fillInIntent.addFlags(FLAG_ACTIVITY_NO_USER_ACTION);
// Flag with MULTIPLE_TASK if this is launching the same activity into both sides of the
// split.
if (isLaunchingAdjacently(intent.getIntent(), position)) {
fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
}
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();
}
// 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.
fillInIntent.addFlags(FLAG_ACTIVITY_NO_USER_ACTION);
// Flag with MULTIPLE_TASK if this is launching the same activity into both sides of the
// split.
if (isLaunchingAdjacently(intent.getIntent(), position)) {
fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
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);
}
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);
@@ -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()));
}
@@ -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);
}

View File

@@ -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;
@@ -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(),
@@ -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)
@@ -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. */
@@ -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)
@@ -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 {
@@ -247,38 +288,55 @@ class SplitScreenTransitions {
}
void onTransitionConsumed(@NonNull IBinder transition, boolean aborted) {
if (aborted) return;
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);
}
// Once a pending enter transition got merged, make sure to append the reset of finishing
// operations to the finish transition.
if (transition == mPendingEnter) {
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) {
@@ -382,17 +440,34 @@ class SplitScreenTransitions {
|| info.getType() == TRANSIT_SPLIT_SCREEN_PAIR_OPEN;
}
/** 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) {}
}
/** Session for a transition and its clean-up callback. */
static class TransitSession {
final IBinder mTransition;
TransitionCallback mCallback;
TransitSession(IBinder transition, @Nullable TransitionCallback callback) {
mTransition = transition;
mCallback = callback != null ? callback : new TransitionCallback() {};
}
}
/** Bundled information of dismiss transition. */
static class DismissTransition {
IBinder mTransition;
int mReason;
@SplitScreen.StageType
int mDismissTop;
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;
}

View File

@@ -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;
@@ -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,
@@ -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;
@@ -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,
@@ -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. */
@@ -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) {
@@ -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();
@@ -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 {
@@ -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;
@@ -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.
@@ -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);
}
@@ -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();

View File

@@ -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,
@@ -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,