Loading libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +44 −40 Original line number Diff line number Diff line Loading @@ -259,37 +259,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } }; private final SplitScreenTransitions.TransitionFinishedCallback mRecentTransitionFinishedCallback = new SplitScreenTransitions.TransitionFinishedCallback() { @Override public void onFinished(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))) { updateSurfaceBounds(mSplitLayout, finishT, false /* applyResizingOffset */); 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); } }; protected StageCoordinator(Context context, int displayId, SyncTransactionQueue syncQueue, ShellTaskOrganizer taskOrganizer, DisplayController displayController, DisplayImeController displayImeController, Loading Loading @@ -388,6 +357,11 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, return mMainStage.isActive(); } /** Checks if `transition` is a pending enter-split transition. */ public boolean isPendingEnter(IBinder transition) { return mSplitTransitions.isPendingEnter(transition); } @StageType int getStageOfTask(int taskId) { if (mMainStage.containsTask(taskId)) { Loading Loading @@ -2264,11 +2238,16 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } } else if (isOpening && inFullscreen) { final int activityType = triggerTask.getActivityType(); if (activityType == ACTIVITY_TYPE_HOME || activityType == ACTIVITY_TYPE_RECENTS) { // Enter overview panel, so start recent transition. mSplitTransitions.setRecentTransition(transition, request.getRemoteTransition(), mRecentTransitionFinishedCallback); if (activityType == ACTIVITY_TYPE_HOME || activityType == ACTIVITY_TYPE_RECENTS) { if (request.getRemoteTransition() != null) { // starting recents/home, so don't handle this and let it fall-through to // the remote handler. return null; } // Need to use the old stuff for non-remote animations, otherwise we don't // exit split-screen. mSplitTransitions.setRecentTransition(transition, null /* remote */, this::onRecentsInSplitAnimationFinish); } } } else { Loading Loading @@ -2398,7 +2377,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, shouldAnimate = startPendingEnterAnimation( transition, info, startTransaction, finishTransaction); } else if (mSplitTransitions.isPendingRecent(transition)) { shouldAnimate = startPendingRecentAnimation(transition, info, startTransaction); onRecentsInSplitAnimationStart(startTransaction); } else if (mSplitTransitions.isPendingDismiss(transition)) { shouldAnimate = startPendingDismissAnimation( mSplitTransitions.mPendingDismiss, info, startTransaction, finishTransaction); Loading Loading @@ -2653,10 +2632,35 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, return true; } private boolean startPendingRecentAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t) { /** Call this when starting the open-recents animation while split-screen is active. */ public void onRecentsInSplitAnimationStart(@NonNull SurfaceControl.Transaction t) { setDividerVisibility(false, t); return true; } /** Call this when the recents animation during split-screen finishes. */ public void onRecentsInSplitAnimationFinish(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))) { updateSurfaceBounds(mSplitLayout, finishT, false /* applyResizingOffset */); 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); } private void addDividerBarToTransition(@NonNull TransitionInfo info, Loading libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java +72 −2 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ package com.android.wm.shell.transition; 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.view.WindowManager.TRANSIT_CHANGE; import static android.view.WindowManager.TRANSIT_TO_BACK; import static android.window.TransitionInfo.FLAG_IS_WALLPAPER; Loading @@ -25,6 +26,7 @@ import static android.window.TransitionInfo.FLAG_IS_WALLPAPER; import static com.android.wm.shell.common.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR; import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED; import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_CHILD_TASK_ENTER_PIP; import static com.android.wm.shell.util.TransitionUtil.isOpeningType; import android.annotation.NonNull; import android.annotation.Nullable; Loading Loading @@ -68,14 +70,20 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler { /** Pip was entered while handling an intent with its own remoteTransition. */ static final int TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE = 3; /** Recents transition while split-screen active. */ static final int TYPE_RECENTS_DURING_SPLIT = 4; /** The default animation for this mixed transition. */ static final int ANIM_TYPE_DEFAULT = 0; /** For ENTER_PIP_FROM_SPLIT, indicates that this is a to-home animation. */ static final int ANIM_TYPE_GOING_HOME = 1; /** For RECENTS_DURING_SPLIT, is set when this turns into a pair->pair task switch. */ static final int ANIM_TYPE_PAIR_TO_PAIR = 1; final int mType; int mAnimType = 0; int mAnimType = ANIM_TYPE_DEFAULT; final IBinder mTransition; Transitions.TransitionHandler mLeftoversHandler = null; Loading Loading @@ -167,6 +175,27 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler { mixed.mLeftoversHandler = handler.first; mActiveTransitions.add(mixed); return handler.second; } else if (mSplitHandler.isSplitActive() && isOpeningType(request.getType()) && request.getTriggerTask() != null && request.getTriggerTask().getWindowingMode() == WINDOWING_MODE_FULLSCREEN && (request.getTriggerTask().getActivityType() == ACTIVITY_TYPE_HOME || request.getTriggerTask().getActivityType() == ACTIVITY_TYPE_RECENTS) && request.getRemoteTransition() != null) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Got a recents request while " + "Split-Screen is active, so treat it as Mixed."); Pair<Transitions.TransitionHandler, WindowContainerTransaction> handler = mPlayer.dispatchRequest(transition, request, this); if (handler == null) { android.util.Log.e(Transitions.TAG, " No handler for remote? This is unexpected" + ", there should at-least be RemoteHandler."); return null; } final MixedTransition mixed = new MixedTransition( MixedTransition.TYPE_RECENTS_DURING_SPLIT, transition); mixed.mLeftoversHandler = handler.first; mActiveTransitions.add(mixed); return handler.second; } return null; } Loading Loading @@ -216,6 +245,9 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler { } else if (mixed.mType == MixedTransition.TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE) { return animateOpenIntentWithRemoteAndPip(mixed, info, startTransaction, finishTransaction, finishCallback); } else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_SPLIT) { return animateRecentsDuringSplit(mixed, info, startTransaction, finishTransaction, finishCallback); } else { mActiveTransitions.remove(mixed); throw new IllegalStateException("Starting mixed animation without a known mixed type? " Loading Loading @@ -441,12 +473,40 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler { return true; } private boolean animateRecentsDuringSplit(@NonNull final MixedTransition mixed, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback) { // Split-screen is only interested in the recents transition finishing (and merging), so // just wrap finish and start recents animation directly. Transitions.TransitionFinishCallback finishCB = (wct, wctCB) -> { mixed.mInFlightSubAnimations = 0; mActiveTransitions.remove(mixed); // If pair-to-pair switching, the post-recents clean-up isn't needed. if (mixed.mAnimType != MixedTransition.ANIM_TYPE_PAIR_TO_PAIR) { wct = wct != null ? wct : new WindowContainerTransaction(); mSplitHandler.onRecentsInSplitAnimationFinish(wct, finishTransaction); } mSplitHandler.onTransitionAnimationComplete(); finishCallback.onTransitionFinished(wct, wctCB); }; mixed.mInFlightSubAnimations = 1; mSplitHandler.onRecentsInSplitAnimationStart(startTransaction); final boolean handled = mixed.mLeftoversHandler.startAnimation(mixed.mTransition, info, startTransaction, finishTransaction, finishCB); if (!handled) { mActiveTransitions.remove(mixed); } return handled; } @Override public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget, @NonNull Transitions.TransitionFinishCallback finishCallback) { for (int i = 0; i < mActiveTransitions.size(); ++i) { if (mActiveTransitions.get(i) != mergeTarget) continue; if (mActiveTransitions.get(i).mTransition != mergeTarget) continue; MixedTransition mixed = mActiveTransitions.get(i); if (mixed.mInFlightSubAnimations <= 0) { // Already done, so no need to end it. Loading Loading @@ -474,6 +534,14 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler { mixed.mLeftoversHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback); } } else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_SPLIT) { if (mSplitHandler.isPendingEnter(transition)) { // Recents -> enter-split means that we are switching from one pair to // another pair. mixed.mAnimType = MixedTransition.ANIM_TYPE_PAIR_TO_PAIR; } mixed.mLeftoversHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback); } else { throw new IllegalStateException("Playing a mixed transition with unknown type? " + mixed.mType); Loading @@ -493,6 +561,8 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler { if (mixed == null) return; if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT) { mPipHandler.onTransitionConsumed(transition, aborted, finishT); } else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_SPLIT) { mixed.mLeftoversHandler.onTransitionConsumed(transition, aborted, finishT); } } } libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java +52 −13 Original line number Diff line number Diff line Loading @@ -35,6 +35,7 @@ import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_SCREEN_P import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; Loading Loading @@ -249,7 +250,7 @@ public class SplitTransitionTests extends ShellTestCase { @Test @UiThreadTest public void testEnterRecents() { public void testEnterRecentsAndCommit() { enterSplit(); ActivityManager.RunningTaskInfo homeTask = new TestRunningTaskInfoBuilder() Loading @@ -258,27 +259,65 @@ public class SplitTransitionTests extends ShellTestCase { .build(); // Create a request to bring home forward TransitionRequestInfo request = new TransitionRequestInfo(TRANSIT_TO_FRONT, homeTask, null); TransitionRequestInfo request = new TransitionRequestInfo(TRANSIT_TO_FRONT, homeTask, mock(RemoteTransition.class)); IBinder transition = mock(IBinder.class); WindowContainerTransaction result = mStageCoordinator.handleRequest(transition, request); assertTrue(result.isEmpty()); // Don't handle recents opening assertNull(result); // make sure we haven't made any local changes yet (need to wait until transition is ready) assertTrue(mStageCoordinator.isSplitScreenVisible()); // simulate the transition TransitionInfo info = new TransitionInfoBuilder(TRANSIT_TO_FRONT, 0) .addChange(TRANSIT_TO_FRONT, homeTask) .addChange(TRANSIT_TO_BACK, mMainChild) .addChange(TRANSIT_TO_BACK, mSideChild) // simulate the start of recents transition mMainStage.onTaskVanished(mMainChild); mSideStage.onTaskVanished(mSideChild); mStageCoordinator.onRecentsInSplitAnimationStart(mock(SurfaceControl.Transaction.class)); assertTrue(mStageCoordinator.isSplitScreenVisible()); // Make sure it cleans-up if recents doesn't restore WindowContainerTransaction commitWCT = new WindowContainerTransaction(); mStageCoordinator.onRecentsInSplitAnimationFinish(commitWCT, mock(SurfaceControl.Transaction.class)); assertFalse(mStageCoordinator.isSplitScreenVisible()); } @Test @UiThreadTest public void testEnterRecentsAndRestore() { enterSplit(); ActivityManager.RunningTaskInfo homeTask = new TestRunningTaskInfoBuilder() .setWindowingMode(WINDOWING_MODE_FULLSCREEN) .setActivityType(ACTIVITY_TYPE_HOME) .build(); // Create a request to bring home forward TransitionRequestInfo request = new TransitionRequestInfo(TRANSIT_TO_FRONT, homeTask, mock(RemoteTransition.class)); IBinder transition = mock(IBinder.class); WindowContainerTransaction result = mStageCoordinator.handleRequest(transition, request); // Don't handle recents opening assertNull(result); // make sure we haven't made any local changes yet (need to wait until transition is ready) assertTrue(mStageCoordinator.isSplitScreenVisible()); // simulate the start of recents transition mMainStage.onTaskVanished(mMainChild); mSideStage.onTaskVanished(mSideChild); mStageCoordinator.startAnimation(transition, info, mock(SurfaceControl.Transaction.class), mock(SurfaceControl.Transaction.class), mock(Transitions.TransitionFinishCallback.class)); mStageCoordinator.onRecentsInSplitAnimationStart(mock(SurfaceControl.Transaction.class)); assertTrue(mStageCoordinator.isSplitScreenVisible()); // Make sure we remain in split after recents restores. WindowContainerTransaction restoreWCT = new WindowContainerTransaction(); restoreWCT.reorder(mMainChild.token, true /* toTop */); restoreWCT.reorder(mSideChild.token, true /* toTop */); // simulate the restoreWCT being applied: mMainStage.onTaskAppeared(mMainChild, mock(SurfaceControl.class)); mSideStage.onTaskAppeared(mSideChild, mock(SurfaceControl.class)); mStageCoordinator.onRecentsInSplitAnimationFinish(restoreWCT, mock(SurfaceControl.Transaction.class)); assertTrue(mStageCoordinator.isSplitScreenVisible()); } Loading Loading
libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +44 −40 Original line number Diff line number Diff line Loading @@ -259,37 +259,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } }; private final SplitScreenTransitions.TransitionFinishedCallback mRecentTransitionFinishedCallback = new SplitScreenTransitions.TransitionFinishedCallback() { @Override public void onFinished(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))) { updateSurfaceBounds(mSplitLayout, finishT, false /* applyResizingOffset */); 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); } }; protected StageCoordinator(Context context, int displayId, SyncTransactionQueue syncQueue, ShellTaskOrganizer taskOrganizer, DisplayController displayController, DisplayImeController displayImeController, Loading Loading @@ -388,6 +357,11 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, return mMainStage.isActive(); } /** Checks if `transition` is a pending enter-split transition. */ public boolean isPendingEnter(IBinder transition) { return mSplitTransitions.isPendingEnter(transition); } @StageType int getStageOfTask(int taskId) { if (mMainStage.containsTask(taskId)) { Loading Loading @@ -2264,11 +2238,16 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } } else if (isOpening && inFullscreen) { final int activityType = triggerTask.getActivityType(); if (activityType == ACTIVITY_TYPE_HOME || activityType == ACTIVITY_TYPE_RECENTS) { // Enter overview panel, so start recent transition. mSplitTransitions.setRecentTransition(transition, request.getRemoteTransition(), mRecentTransitionFinishedCallback); if (activityType == ACTIVITY_TYPE_HOME || activityType == ACTIVITY_TYPE_RECENTS) { if (request.getRemoteTransition() != null) { // starting recents/home, so don't handle this and let it fall-through to // the remote handler. return null; } // Need to use the old stuff for non-remote animations, otherwise we don't // exit split-screen. mSplitTransitions.setRecentTransition(transition, null /* remote */, this::onRecentsInSplitAnimationFinish); } } } else { Loading Loading @@ -2398,7 +2377,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, shouldAnimate = startPendingEnterAnimation( transition, info, startTransaction, finishTransaction); } else if (mSplitTransitions.isPendingRecent(transition)) { shouldAnimate = startPendingRecentAnimation(transition, info, startTransaction); onRecentsInSplitAnimationStart(startTransaction); } else if (mSplitTransitions.isPendingDismiss(transition)) { shouldAnimate = startPendingDismissAnimation( mSplitTransitions.mPendingDismiss, info, startTransaction, finishTransaction); Loading Loading @@ -2653,10 +2632,35 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, return true; } private boolean startPendingRecentAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t) { /** Call this when starting the open-recents animation while split-screen is active. */ public void onRecentsInSplitAnimationStart(@NonNull SurfaceControl.Transaction t) { setDividerVisibility(false, t); return true; } /** Call this when the recents animation during split-screen finishes. */ public void onRecentsInSplitAnimationFinish(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))) { updateSurfaceBounds(mSplitLayout, finishT, false /* applyResizingOffset */); 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); } private void addDividerBarToTransition(@NonNull TransitionInfo info, Loading
libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java +72 −2 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ package com.android.wm.shell.transition; 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.view.WindowManager.TRANSIT_CHANGE; import static android.view.WindowManager.TRANSIT_TO_BACK; import static android.window.TransitionInfo.FLAG_IS_WALLPAPER; Loading @@ -25,6 +26,7 @@ import static android.window.TransitionInfo.FLAG_IS_WALLPAPER; import static com.android.wm.shell.common.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR; import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED; import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_CHILD_TASK_ENTER_PIP; import static com.android.wm.shell.util.TransitionUtil.isOpeningType; import android.annotation.NonNull; import android.annotation.Nullable; Loading Loading @@ -68,14 +70,20 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler { /** Pip was entered while handling an intent with its own remoteTransition. */ static final int TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE = 3; /** Recents transition while split-screen active. */ static final int TYPE_RECENTS_DURING_SPLIT = 4; /** The default animation for this mixed transition. */ static final int ANIM_TYPE_DEFAULT = 0; /** For ENTER_PIP_FROM_SPLIT, indicates that this is a to-home animation. */ static final int ANIM_TYPE_GOING_HOME = 1; /** For RECENTS_DURING_SPLIT, is set when this turns into a pair->pair task switch. */ static final int ANIM_TYPE_PAIR_TO_PAIR = 1; final int mType; int mAnimType = 0; int mAnimType = ANIM_TYPE_DEFAULT; final IBinder mTransition; Transitions.TransitionHandler mLeftoversHandler = null; Loading Loading @@ -167,6 +175,27 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler { mixed.mLeftoversHandler = handler.first; mActiveTransitions.add(mixed); return handler.second; } else if (mSplitHandler.isSplitActive() && isOpeningType(request.getType()) && request.getTriggerTask() != null && request.getTriggerTask().getWindowingMode() == WINDOWING_MODE_FULLSCREEN && (request.getTriggerTask().getActivityType() == ACTIVITY_TYPE_HOME || request.getTriggerTask().getActivityType() == ACTIVITY_TYPE_RECENTS) && request.getRemoteTransition() != null) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Got a recents request while " + "Split-Screen is active, so treat it as Mixed."); Pair<Transitions.TransitionHandler, WindowContainerTransaction> handler = mPlayer.dispatchRequest(transition, request, this); if (handler == null) { android.util.Log.e(Transitions.TAG, " No handler for remote? This is unexpected" + ", there should at-least be RemoteHandler."); return null; } final MixedTransition mixed = new MixedTransition( MixedTransition.TYPE_RECENTS_DURING_SPLIT, transition); mixed.mLeftoversHandler = handler.first; mActiveTransitions.add(mixed); return handler.second; } return null; } Loading Loading @@ -216,6 +245,9 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler { } else if (mixed.mType == MixedTransition.TYPE_OPTIONS_REMOTE_AND_PIP_CHANGE) { return animateOpenIntentWithRemoteAndPip(mixed, info, startTransaction, finishTransaction, finishCallback); } else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_SPLIT) { return animateRecentsDuringSplit(mixed, info, startTransaction, finishTransaction, finishCallback); } else { mActiveTransitions.remove(mixed); throw new IllegalStateException("Starting mixed animation without a known mixed type? " Loading Loading @@ -441,12 +473,40 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler { return true; } private boolean animateRecentsDuringSplit(@NonNull final MixedTransition mixed, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback) { // Split-screen is only interested in the recents transition finishing (and merging), so // just wrap finish and start recents animation directly. Transitions.TransitionFinishCallback finishCB = (wct, wctCB) -> { mixed.mInFlightSubAnimations = 0; mActiveTransitions.remove(mixed); // If pair-to-pair switching, the post-recents clean-up isn't needed. if (mixed.mAnimType != MixedTransition.ANIM_TYPE_PAIR_TO_PAIR) { wct = wct != null ? wct : new WindowContainerTransaction(); mSplitHandler.onRecentsInSplitAnimationFinish(wct, finishTransaction); } mSplitHandler.onTransitionAnimationComplete(); finishCallback.onTransitionFinished(wct, wctCB); }; mixed.mInFlightSubAnimations = 1; mSplitHandler.onRecentsInSplitAnimationStart(startTransaction); final boolean handled = mixed.mLeftoversHandler.startAnimation(mixed.mTransition, info, startTransaction, finishTransaction, finishCB); if (!handled) { mActiveTransitions.remove(mixed); } return handled; } @Override public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget, @NonNull Transitions.TransitionFinishCallback finishCallback) { for (int i = 0; i < mActiveTransitions.size(); ++i) { if (mActiveTransitions.get(i) != mergeTarget) continue; if (mActiveTransitions.get(i).mTransition != mergeTarget) continue; MixedTransition mixed = mActiveTransitions.get(i); if (mixed.mInFlightSubAnimations <= 0) { // Already done, so no need to end it. Loading Loading @@ -474,6 +534,14 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler { mixed.mLeftoversHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback); } } else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_SPLIT) { if (mSplitHandler.isPendingEnter(transition)) { // Recents -> enter-split means that we are switching from one pair to // another pair. mixed.mAnimType = MixedTransition.ANIM_TYPE_PAIR_TO_PAIR; } mixed.mLeftoversHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback); } else { throw new IllegalStateException("Playing a mixed transition with unknown type? " + mixed.mType); Loading @@ -493,6 +561,8 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler { if (mixed == null) return; if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT) { mPipHandler.onTransitionConsumed(transition, aborted, finishT); } else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_SPLIT) { mixed.mLeftoversHandler.onTransitionConsumed(transition, aborted, finishT); } } }
libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java +52 −13 Original line number Diff line number Diff line Loading @@ -35,6 +35,7 @@ import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_SCREEN_P import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; Loading Loading @@ -249,7 +250,7 @@ public class SplitTransitionTests extends ShellTestCase { @Test @UiThreadTest public void testEnterRecents() { public void testEnterRecentsAndCommit() { enterSplit(); ActivityManager.RunningTaskInfo homeTask = new TestRunningTaskInfoBuilder() Loading @@ -258,27 +259,65 @@ public class SplitTransitionTests extends ShellTestCase { .build(); // Create a request to bring home forward TransitionRequestInfo request = new TransitionRequestInfo(TRANSIT_TO_FRONT, homeTask, null); TransitionRequestInfo request = new TransitionRequestInfo(TRANSIT_TO_FRONT, homeTask, mock(RemoteTransition.class)); IBinder transition = mock(IBinder.class); WindowContainerTransaction result = mStageCoordinator.handleRequest(transition, request); assertTrue(result.isEmpty()); // Don't handle recents opening assertNull(result); // make sure we haven't made any local changes yet (need to wait until transition is ready) assertTrue(mStageCoordinator.isSplitScreenVisible()); // simulate the transition TransitionInfo info = new TransitionInfoBuilder(TRANSIT_TO_FRONT, 0) .addChange(TRANSIT_TO_FRONT, homeTask) .addChange(TRANSIT_TO_BACK, mMainChild) .addChange(TRANSIT_TO_BACK, mSideChild) // simulate the start of recents transition mMainStage.onTaskVanished(mMainChild); mSideStage.onTaskVanished(mSideChild); mStageCoordinator.onRecentsInSplitAnimationStart(mock(SurfaceControl.Transaction.class)); assertTrue(mStageCoordinator.isSplitScreenVisible()); // Make sure it cleans-up if recents doesn't restore WindowContainerTransaction commitWCT = new WindowContainerTransaction(); mStageCoordinator.onRecentsInSplitAnimationFinish(commitWCT, mock(SurfaceControl.Transaction.class)); assertFalse(mStageCoordinator.isSplitScreenVisible()); } @Test @UiThreadTest public void testEnterRecentsAndRestore() { enterSplit(); ActivityManager.RunningTaskInfo homeTask = new TestRunningTaskInfoBuilder() .setWindowingMode(WINDOWING_MODE_FULLSCREEN) .setActivityType(ACTIVITY_TYPE_HOME) .build(); // Create a request to bring home forward TransitionRequestInfo request = new TransitionRequestInfo(TRANSIT_TO_FRONT, homeTask, mock(RemoteTransition.class)); IBinder transition = mock(IBinder.class); WindowContainerTransaction result = mStageCoordinator.handleRequest(transition, request); // Don't handle recents opening assertNull(result); // make sure we haven't made any local changes yet (need to wait until transition is ready) assertTrue(mStageCoordinator.isSplitScreenVisible()); // simulate the start of recents transition mMainStage.onTaskVanished(mMainChild); mSideStage.onTaskVanished(mSideChild); mStageCoordinator.startAnimation(transition, info, mock(SurfaceControl.Transaction.class), mock(SurfaceControl.Transaction.class), mock(Transitions.TransitionFinishCallback.class)); mStageCoordinator.onRecentsInSplitAnimationStart(mock(SurfaceControl.Transaction.class)); assertTrue(mStageCoordinator.isSplitScreenVisible()); // Make sure we remain in split after recents restores. WindowContainerTransaction restoreWCT = new WindowContainerTransaction(); restoreWCT.reorder(mMainChild.token, true /* toTop */); restoreWCT.reorder(mSideChild.token, true /* toTop */); // simulate the restoreWCT being applied: mMainStage.onTaskAppeared(mMainChild, mock(SurfaceControl.class)); mSideStage.onTaskAppeared(mSideChild, mock(SurfaceControl.class)); mStageCoordinator.onRecentsInSplitAnimationFinish(restoreWCT, mock(SurfaceControl.Transaction.class)); assertTrue(mStageCoordinator.isSplitScreenVisible()); } Loading