From d656ee9d2800109ce514370e040b44e49d244f3b Mon Sep 17 00:00:00 2001 From: Tony Huang Date: Fri, 17 Jun 2022 18:31:41 +0800 Subject: [PATCH] Improve dismiss split transition Improve this transition by seperating it as 2 transitions: 1. Only dismiss invisible split side and extand another one side root task as full screen bounds. 2. After first transition finished, fade-out the decor surface and reparent the child task to TDA top. Bug: 200245358 Test: manual Change-Id: I1a0c4d1715765e7de0ce2628e978cab392d2b07e --- .../shell/common/split/SplitDecorManager.java | 28 +++++-- .../wm/shell/common/split/SplitLayout.java | 17 +++- .../wm/shell/splitscreen/MainStage.java | 5 ++ .../wm/shell/splitscreen/SideStage.java | 5 ++ .../splitscreen/SplitScreenController.java | 20 +++-- .../shell/splitscreen/StageCoordinator.java | 81 ++++++++++++++----- .../shell/splitscreen/StageTaskListener.java | 22 ++++- .../shell/common/split/SplitLayoutTests.java | 4 +- .../splitscreen/StageCoordinatorTests.java | 11 ++- 9 files changed, 144 insertions(+), 49 deletions(-) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java index 6fb021ea5e2fc..74f8bf9ac8636 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java @@ -50,6 +50,8 @@ import com.android.launcher3.icons.IconProvider; import com.android.wm.shell.R; import com.android.wm.shell.common.SurfaceUtils; +import java.util.function.Consumer; + /** * Handles split decor like showing resizing hint for a specific split. */ @@ -212,7 +214,7 @@ public class SplitDecorManager extends WindowlessWindowManager { newBounds.height() / 2 - mIconSize / 2); if (animate) { - startFadeAnimation(show, false /* isResized */); + startFadeAnimation(show, null /* finishedConsumer */); mShown = show; } } @@ -243,15 +245,29 @@ public class SplitDecorManager extends WindowlessWindowManager { mFadeAnimator.cancel(); } if (mShown) { - startFadeAnimation(false /* show */, true /* isResized */); - mShown = false; + fadeOutDecor(null /* finishedCallback */); } else { // Decor surface is hidden so release it directly. releaseDecor(t); } } - private void startFadeAnimation(boolean show, boolean isResized) { + /** Fade-out decor surface with animation end callback, if decor is hidden, run the callback + * directly. */ + public void fadeOutDecor(Runnable finishedCallback) { + if (mShown) { + startFadeAnimation(false /* show */, transaction -> { + releaseDecor(transaction); + if (finishedCallback != null) finishedCallback.run(); + }); + mShown = false; + } else { + if (finishedCallback != null) finishedCallback.run(); + } + } + + private void startFadeAnimation(boolean show, + Consumer finishedConsumer) { final SurfaceControl.Transaction animT = new SurfaceControl.Transaction(); mFadeAnimator = ValueAnimator.ofFloat(0f, 1f); mFadeAnimator.setDuration(FADE_DURATION); @@ -285,8 +301,8 @@ public class SplitDecorManager extends WindowlessWindowManager { animT.hide(mIconLeash); } } - if (isResized) { - releaseDecor(animT); + if (finishedConsumer != null) { + finishedConsumer.accept(animT); } animT.apply(); animT.close(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java index 2e64c945ca252..b69cbfa83d7c2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java @@ -32,6 +32,7 @@ import static com.android.wm.shell.animation.Interpolators.SLOWDOWN_INTERPOLATOR import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT; import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT; import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED; +import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DRAG_DIVIDER; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; @@ -449,11 +450,13 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange switch (snapTarget.flag) { case FLAG_DISMISS_START: flingDividePosition(currentPosition, snapTarget.position, - () -> mSplitLayoutHandler.onSnappedToDismiss(false /* bottomOrRight */)); + () -> mSplitLayoutHandler.onSnappedToDismiss(false /* bottomOrRight */, + EXIT_REASON_DRAG_DIVIDER)); break; case FLAG_DISMISS_END: flingDividePosition(currentPosition, snapTarget.position, - () -> mSplitLayoutHandler.onSnappedToDismiss(true /* bottomOrRight */)); + () -> mSplitLayoutHandler.onSnappedToDismiss(true /* bottomOrRight */, + EXIT_REASON_DRAG_DIVIDER)); break; default: flingDividePosition(currentPosition, snapTarget.position, @@ -509,6 +512,14 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange isLandscape ? DOCKED_LEFT : DOCKED_TOP /* dockSide */); } + /** Fling divider from current position to end or start position then exit */ + public void flingDividerToDismiss(boolean toEnd, int reason) { + final int target = toEnd ? mDividerSnapAlgorithm.getDismissEndTarget().position + : mDividerSnapAlgorithm.getDismissStartTarget().position; + flingDividePosition(getDividePosition(), target, + () -> mSplitLayoutHandler.onSnappedToDismiss(toEnd, reason)); + } + @VisibleForTesting void flingDividePosition(int from, int to, @Nullable Runnable flingFinishedCallback) { if (from == to) { @@ -758,7 +769,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange public interface SplitLayoutHandler { /** Calls when dismissing split. */ - void onSnappedToDismiss(boolean snappedToEnd); + void onSnappedToDismiss(boolean snappedToEnd, int reason); /** * Calls when resizing the split bounds. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java index e7ec15e70c11c..b0080b24c6092 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java @@ -45,6 +45,11 @@ class MainStage extends StageTaskListener { iconProvider); } + @Override + void dismiss(WindowContainerTransaction wct, boolean toTop) { + deactivate(wct, toTop); + } + boolean isActive() { return mIsActive; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java index 8639b36faf4c2..86efbe0af79c0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java @@ -42,6 +42,11 @@ class SideStage extends StageTaskListener { iconProvider); } + @Override + void dismiss(WindowContainerTransaction wct, boolean toTop) { + removeAllTasks(wct, toTop); + } + boolean removeAllTasks(WindowContainerTransaction wct, boolean toTop) { if (mChildrenTaskInfo.size() == 0) return false; wct.reparentTasks( diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java index 47bfaa64c831f..249468d1446ca 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java @@ -18,8 +18,6 @@ package com.android.wm.shell.splitscreen; import static android.app.ActivityManager.START_SUCCESS; import static android.app.ActivityManager.START_TASK_TO_FRONT; -import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; -import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK; import static android.content.Intent.FLAG_ACTIVITY_NO_USER_ACTION; import static android.view.Display.DEFAULT_DISPLAY; @@ -107,15 +105,15 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, RemoteCallable, ShellTaskOrganizer.FocusListener { private static final String TAG = SplitScreenController.class.getSimpleName(); - static final int EXIT_REASON_UNKNOWN = 0; - static final int EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW = 1; - static final int EXIT_REASON_APP_FINISHED = 2; - static final int EXIT_REASON_DEVICE_FOLDED = 3; - static final int EXIT_REASON_DRAG_DIVIDER = 4; - static final int EXIT_REASON_RETURN_HOME = 5; - static final int EXIT_REASON_ROOT_TASK_VANISHED = 6; - static final int EXIT_REASON_SCREEN_LOCKED = 7; - static final int EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP = 8; + public static final int EXIT_REASON_UNKNOWN = 0; + public static final int EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW = 1; + public static final int EXIT_REASON_APP_FINISHED = 2; + public static final int EXIT_REASON_DEVICE_FOLDED = 3; + public static final int EXIT_REASON_DRAG_DIVIDER = 4; + public static final int EXIT_REASON_RETURN_HOME = 5; + public static final int EXIT_REASON_ROOT_TASK_VANISHED = 6; + public static final int EXIT_REASON_SCREEN_LOCKED = 7; + public static final int EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP = 8; public static final int EXIT_REASON_CHILD_TASK_ENTER_PIP = 9; @IntDef(value = { EXIT_REASON_UNKNOWN, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index 2764f96022b9c..f1419e0a5a54d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -189,6 +189,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, private boolean mShouldUpdateRecents; private boolean mExitSplitScreenOnHide; private boolean mIsDividerRemoteAnimating; + private boolean mIsExiting; private boolean mResizingSplits; /** The target stage to dismiss to when unlock after folded. */ @@ -734,7 +735,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, private void applyExitSplitScreen(@Nullable StageTaskListener childrenToTop, WindowContainerTransaction wct, @ExitReason int exitReason) { - if (!mMainStage.isActive()) return; + if (!mMainStage.isActive() || mIsExiting) return; mRecentTasks.ifPresent(recentTasks -> { // Notify recents if we are exiting in a way that breaks the pair, and disable further @@ -746,21 +747,45 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, }); mShouldUpdateRecents = false; - // When the exit split-screen is caused by one of the task enters auto pip, - // we want the tasks to be put to bottom instead of top, otherwise it will end up - // a fullscreen plus a pinned task instead of pinned only at the end of the transition. - final boolean fromEnteringPip = exitReason == EXIT_REASON_CHILD_TASK_ENTER_PIP; - mSideStage.removeAllTasks(wct, !fromEnteringPip && mSideStage == childrenToTop); - mMainStage.deactivate(wct, !fromEnteringPip && mMainStage == childrenToTop); - wct.reorder(mRootTaskInfo.token, false /* onTop */); - mTaskOrganizer.applyTransaction(wct); + if (childrenToTop == null) { + mSideStage.removeAllTasks(wct, false /* toTop */); + mMainStage.deactivate(wct, false /* toTop */); + wct.reorder(mRootTaskInfo.token, false /* onTop */); + onTransitionAnimationComplete(); + } else { + // Expand to top side split as full screen for fading out decor animation and dismiss + // another side split(Moving its children to bottom). + mIsExiting = true; + final StageTaskListener tempFullStage = childrenToTop; + final StageTaskListener dismissStage = mMainStage == childrenToTop + ? mSideStage : mMainStage; + tempFullStage.resetBounds(wct); + wct.setSmallestScreenWidthDp(tempFullStage.mRootTaskInfo.token, + mRootTaskInfo.configuration.smallestScreenWidthDp); + dismissStage.dismiss(wct, false /* toTop */); + } + mSyncQueue.queue(wct); mSyncQueue.runInSync(t -> { t.setWindowCrop(mMainStage.mRootLeash, null) .setWindowCrop(mSideStage.mRootLeash, null); + t.setPosition(mMainStage.mRootLeash, 0, 0) + .setPosition(mSideStage.mRootLeash, 0, 0); setDividerVisibility(false, t); + + // In this case, exit still under progress, fade out the split decor after first WCT + // done and do remaining WCT after animation finished. + if (childrenToTop != null) { + childrenToTop.fadeOutDecor(() -> { + WindowContainerTransaction finishedWCT = new WindowContainerTransaction(); + mIsExiting = false; + childrenToTop.dismiss(finishedWCT, true /* toTop */); + wct.reorder(mRootTaskInfo.token, false /* toTop */); + mTaskOrganizer.applyTransaction(finishedWCT); + onTransitionAnimationComplete(); + }); + } }); - onTransitionAnimationComplete(); Slog.i(TAG, "applyExitSplitScreen, reason = " + exitReasonToString(exitReason)); // Log the exit if (childrenToTop != null) { @@ -930,9 +955,11 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } } - private void onStageChildTaskEnterPip(StageListenerImpl stageListener, int taskId) { - exitSplitScreen(stageListener == mMainStageListener ? mMainStage : mSideStage, - EXIT_REASON_CHILD_TASK_ENTER_PIP); + private void onStageChildTaskEnterPip() { + // When the exit split-screen is caused by one of the task enters auto pip, + // we want both tasks to be put to bottom instead of top, otherwise it will end up + // a fullscreen plus a pinned task instead of pinned only at the end of the transition. + exitSplitScreen(null, EXIT_REASON_CHILD_TASK_ENTER_PIP); } private void updateRecentTasksSplitPair() { @@ -1178,13 +1205,25 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, private void onStageHasChildrenChanged(StageListenerImpl stageListener) { final boolean hasChildren = stageListener.mHasChildren; final boolean isSideStage = stageListener == mSideStageListener; - if (!hasChildren) { + if (!hasChildren && !mIsExiting) { if (isSideStage && mMainStageListener.mVisible) { // Exit to main stage if side stage no longer has children. - exitSplitScreen(mMainStage, EXIT_REASON_APP_FINISHED); + if (ENABLE_SHELL_TRANSITIONS) { + exitSplitScreen(mMainStage, EXIT_REASON_APP_FINISHED); + } else { + mSplitLayout.flingDividerToDismiss( + mSideStagePosition == SPLIT_POSITION_BOTTOM_OR_RIGHT, + EXIT_REASON_APP_FINISHED); + } } else if (!isSideStage && mSideStageListener.mVisible) { // Exit to side stage if main stage no longer has children. - exitSplitScreen(mSideStage, EXIT_REASON_APP_FINISHED); + if (ENABLE_SHELL_TRANSITIONS) { + exitSplitScreen(mSideStage, EXIT_REASON_APP_FINISHED); + } else { + mSplitLayout.flingDividerToDismiss( + mSideStagePosition != SPLIT_POSITION_BOTTOM_OR_RIGHT, + EXIT_REASON_APP_FINISHED); + } } } else if (isSideStage && !mMainStage.isActive()) { final WindowContainerTransaction wct = new WindowContainerTransaction(); @@ -1208,12 +1247,12 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } @Override - public void onSnappedToDismiss(boolean bottomOrRight) { + public void onSnappedToDismiss(boolean bottomOrRight, int reason) { final boolean mainStageToTop = bottomOrRight ? mSideStagePosition == SPLIT_POSITION_BOTTOM_OR_RIGHT : mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT; if (!ENABLE_SHELL_TRANSITIONS) { - exitSplitScreen(mainStageToTop ? mMainStage : mSideStage, EXIT_REASON_DRAG_DIVIDER); + exitSplitScreen(mainStageToTop ? mMainStage : mSideStage, reason); return; } @@ -1615,7 +1654,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, /** Called to clean-up state and do house-keeping after the animation is done. */ public void onTransitionAnimationComplete() { // If still playing, let it finish. - if (!mMainStage.isActive()) { + if (!mMainStage.isActive() && !mIsExiting) { // Update divider state after animation so that it is still around and positioned // properly for the animation itself. mSplitLayout.release(); @@ -1911,8 +1950,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } @Override - public void onChildTaskEnterPip(int taskId) { - StageCoordinator.this.onStageChildTaskEnterPip(this, taskId); + public void onChildTaskEnterPip() { + StageCoordinator.this.onStageChildTaskEnterPip(); } @Override diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java index d17ff7aff6675..f6dc68be946ab 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java @@ -71,7 +71,7 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener { void onChildTaskStatusChanged(int taskId, boolean present, boolean visible); - void onChildTaskEnterPip(int taskId); + void onChildTaskEnterPip(); void onRootTaskVanished(); @@ -103,6 +103,11 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener { taskOrganizer.createRootTask(displayId, WINDOWING_MODE_MULTI_WINDOW, this); } + /** + * General function for dismiss this stage. + */ + void dismiss(WindowContainerTransaction wct, boolean toTop) {} + int getChildCount() { return mChildrenTaskInfo.size(); } @@ -255,7 +260,7 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener { return; } if (taskInfo.getWindowingMode() == WINDOWING_MODE_PINNED) { - mCallbacks.onChildTaskEnterPip(taskId); + mCallbacks.onChildTaskEnterPip(); } sendStatusChanged(); } else { @@ -297,6 +302,14 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener { } } + void fadeOutDecor(Runnable finishedCallback) { + if (mSplitDecorManager != null) { + mSplitDecorManager.fadeOutDecor(finishedCallback); + } else { + finishedCallback.run(); + } + } + void addTask(ActivityManager.RunningTaskInfo task, WindowContainerTransaction wct) { // Clear overridden bounds and windowing mode to make sure the child task can inherit // windowing mode and bounds from split root. @@ -330,6 +343,11 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener { } } + void resetBounds(WindowContainerTransaction wct) { + wct.setBounds(mRootTaskInfo.token, null); + wct.setAppBounds(mRootTaskInfo.token, null); + } + void onSplitScreenListenerRegistered(SplitScreen.SplitScreenListener listener, @StageType int stage) { for (int i = mChildrenTaskInfo.size() - 1; i >= 0; --i) { diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java index f1e602fcf7782..95725bbfd8552 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java @@ -133,7 +133,7 @@ public class SplitLayoutTests extends ShellTestCase { mSplitLayout.snapToTarget(0 /* currentPosition */, snapTarget); waitDividerFlingFinished(); - verify(mSplitLayoutHandler).onSnappedToDismiss(eq(false)); + verify(mSplitLayoutHandler).onSnappedToDismiss(eq(false), anyInt()); } @Test @@ -145,7 +145,7 @@ public class SplitLayoutTests extends ShellTestCase { mSplitLayout.snapToTarget(0 /* currentPosition */, snapTarget); waitDividerFlingFinished(); - verify(mSplitLayoutHandler).onSnappedToDismiss(eq(true)); + verify(mSplitLayoutHandler).onSnappedToDismiss(eq(true), anyInt()); } @Test diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java index af2c495c85c5e..4b68870d4129f 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java @@ -127,6 +127,9 @@ public class StageCoordinatorTests extends ShellTestCase { mRootTask = new TestRunningTaskInfoBuilder().build(); mRootLeash = new SurfaceControl.Builder(mSurfaceSession).setName("test").build(); mStageCoordinator.onTaskAppeared(mRootTask, mRootLeash); + + mSideStage.mRootTaskInfo = new TestRunningTaskInfoBuilder().build(); + mMainStage.mRootTaskInfo = new TestRunningTaskInfoBuilder().build(); } @Test @@ -224,8 +227,8 @@ public class StageCoordinatorTests extends ShellTestCase { mStageCoordinator.exitSplitScreen(testTaskId, EXIT_REASON_RETURN_HOME); verify(mMainStage).reorderChild(eq(testTaskId), eq(true), any(WindowContainerTransaction.class)); - verify(mSideStage).removeAllTasks(any(WindowContainerTransaction.class), eq(false)); - verify(mMainStage).deactivate(any(WindowContainerTransaction.class), eq(true)); + verify(mSideStage).dismiss(any(WindowContainerTransaction.class), eq(false)); + verify(mMainStage).resetBounds(any(WindowContainerTransaction.class)); } @Test @@ -237,8 +240,8 @@ public class StageCoordinatorTests extends ShellTestCase { mStageCoordinator.exitSplitScreen(testTaskId, EXIT_REASON_RETURN_HOME); verify(mSideStage).reorderChild(eq(testTaskId), eq(true), any(WindowContainerTransaction.class)); - verify(mSideStage).removeAllTasks(any(WindowContainerTransaction.class), eq(true)); - verify(mMainStage).deactivate(any(WindowContainerTransaction.class), eq(false)); + verify(mSideStage).resetBounds(any(WindowContainerTransaction.class)); + verify(mMainStage).dismiss(any(WindowContainerTransaction.class), eq(false)); } @Test