Move recents-during-split handling to MixedHandler

This is a mixed operation so it shouldn't be handled
internally to split. Also, this will be needed once
recents are directly requested vs. coming from Core.

Bug: 269691848
Test: moving code around, so existing tests pass.
Change-Id: I906ee42e3b7cf1f67fd40abd227502fa568bef14
This commit is contained in:
Evan Rosky
2023-03-09 21:12:03 -08:00
parent cc10548a5f
commit 74b6fd421a
3 changed files with 169 additions and 56 deletions

View File

@@ -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,
@@ -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)) {
@@ -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 {
@@ -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);
@@ -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,

View File

@@ -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;
@@ -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;
@@ -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;
@@ -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;
}
@@ -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? "
@@ -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.
@@ -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);
@@ -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);
}
}
}

View File

@@ -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;
@@ -249,7 +250,7 @@ public class SplitTransitionTests extends ShellTestCase {
@Test
@UiThreadTest
public void testEnterRecents() {
public void testEnterRecentsAndCommit() {
enterSplit();
ActivityManager.RunningTaskInfo homeTask = new TestRunningTaskInfoBuilder()
@@ -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)
.build();
// 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 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.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());
}