Merge "Integrate launch-to-side split transition to shell transition"

This commit is contained in:
Jerry Chang
2021-12-15 06:51:04 +00:00
committed by Android (Google) Code Review
4 changed files with 110 additions and 71 deletions

View File

@@ -318,8 +318,15 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
startIntentLegacy(intent, fillInIntent, position, options);
return;
}
mStageCoordinator.startIntent(intent, fillInIntent, STAGE_TYPE_UNDEFINED, position, options,
null /* remote */);
try {
options = mStageCoordinator.resolveStartStage(STAGE_TYPE_UNDEFINED, position, options,
null /* wct */);
intent.send(mContext, 0, fillInIntent, null /* onFinished */, null /* handler */,
null /* requiredPermission */, options);
} catch (PendingIntent.CanceledException e) {
Slog.e(TAG, "Failed to launch task", e);
}
}
private void startIntentLegacy(PendingIntent intent, Intent fillInIntent,

View File

@@ -44,7 +44,6 @@ import static com.android.wm.shell.splitscreen.SplitScreenController.exitReasonT
import static com.android.wm.shell.splitscreen.SplitScreenTransitions.FLAG_IS_DIVIDER_BAR;
import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_DISMISS_SNAP;
import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE;
import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_SCREEN_PAIR_OPEN;
import static com.android.wm.shell.transition.Transitions.isClosingType;
import static com.android.wm.shell.transition.Transitions.isOpeningType;
@@ -54,10 +53,8 @@ import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.app.ActivityTaskManager;
import android.app.PendingIntent;
import android.app.WindowConfiguration;
import android.content.Context;
import android.content.Intent;
import android.graphics.Rect;
import android.hardware.devicestate.DeviceStateManager;
import android.os.Bundle;
@@ -436,17 +433,6 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mTaskOrganizer.applyTransaction(wct);
}
public void startIntent(PendingIntent intent, Intent fillInIntent,
@StageType int stage, @SplitPosition int position,
@androidx.annotation.Nullable Bundle options,
@Nullable RemoteTransition remoteTransition) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
options = resolveStartStage(stage, position, options, wct);
wct.sendPendingIntent(intent, fillInIntent, options);
mSplitTransitions.startEnterTransition(
TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE, wct, remoteTransition, this);
}
/**
* Collects all the current child tasks of a specific split and prepares transaction to evict
* them to display.
@@ -1158,12 +1144,14 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mSplitTransitions.mPendingDismiss = transition;
}
} else {
// Not in split mode, so look for an open into a split stage just so we can whine and
// complain about how this isn't a supported operation.
// Not in split mode, so look for an open into a split stage to active split screen.
if ((type == TRANSIT_OPEN || type == TRANSIT_TO_FRONT)) {
if (getStageOfTask(triggerTask) != null) {
throw new IllegalStateException("Entering split implicitly with only one task"
+ " isn't supported.");
// One task is appearing in split, prepare to enter split screen.
out = new WindowContainerTransaction();
mSplitTransitions.mPendingEnter = transition;
mMainStage.activate(getMainStageBounds(), out, true /* includingTopTask */);
mSideStage.moveToTop(getSideStageBounds(), out);
}
}
}
@@ -1232,57 +1220,50 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
private boolean startPendingEnterAnimation(@NonNull IBinder transition,
@NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t) {
if (info.getType() == TRANSIT_SPLIT_SCREEN_PAIR_OPEN) {
// First, verify that we actually have opened 2 apps in split.
TransitionInfo.Change mainChild = null;
TransitionInfo.Change sideChild = null;
for (int iC = 0; iC < info.getChanges().size(); ++iC) {
final TransitionInfo.Change change = info.getChanges().get(iC);
final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
if (taskInfo == null || !taskInfo.hasParentTask()) continue;
final @StageType int stageType = getStageType(getStageOfTask(taskInfo));
if (stageType == STAGE_TYPE_MAIN) {
mainChild = change;
} else if (stageType == STAGE_TYPE_SIDE) {
sideChild = change;
}
// First, verify that we actually have opened apps in both splits.
TransitionInfo.Change mainChild = null;
TransitionInfo.Change sideChild = null;
for (int iC = 0; iC < info.getChanges().size(); ++iC) {
final TransitionInfo.Change change = info.getChanges().get(iC);
final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
if (taskInfo == null || !taskInfo.hasParentTask()) continue;
final @StageType int stageType = getStageType(getStageOfTask(taskInfo));
if (stageType == STAGE_TYPE_MAIN) {
mainChild = change;
} else if (stageType == STAGE_TYPE_SIDE) {
sideChild = change;
}
if (mainChild == null || sideChild == null) {
throw new IllegalStateException("Launched 2 tasks in split, but didn't receive"
+ " 2 tasks in transition. Possibly one of them failed to launch");
// TODO: fallback logic. Probably start a new transition to exit split before
// applying anything here. Ideally consolidate with transition-merging.
}
// Update local states (before animating).
setDividerVisibility(true);
setSideStagePosition(SPLIT_POSITION_BOTTOM_OR_RIGHT, false /* updateBounds */,
null /* wct */);
setSplitsVisible(true);
addDividerBarToTransition(info, t, true /* show */);
// Make some noise if things aren't totally expected. These states shouldn't effect
// transitions locally, but remotes (like Launcher) may get confused if they were
// depending on listener callbacks. This can happen because task-organizer callbacks
// aren't serialized with transition callbacks.
// TODO(b/184679596): Find a way to either include task-org information in
// the transition, or synchronize task-org callbacks.
if (!mMainStage.containsTask(mainChild.getTaskInfo().taskId)) {
Log.w(TAG, "Expected onTaskAppeared on " + mMainStage
+ " to have been called with " + mainChild.getTaskInfo().taskId
+ " before startAnimation().");
}
if (!mSideStage.containsTask(sideChild.getTaskInfo().taskId)) {
Log.w(TAG, "Expected onTaskAppeared on " + mSideStage
+ " to have been called with " + sideChild.getTaskInfo().taskId
+ " before startAnimation().");
}
return true;
} else {
// TODO: other entry method animations
throw new RuntimeException("Unsupported split-entry");
}
if (mainChild == null || sideChild == null) {
throw new IllegalStateException("Launched 2 tasks in split, but didn't receive"
+ " 2 tasks in transition. Possibly one of them failed to launch");
// TODO: fallback logic. Probably start a new transition to exit split before
// applying anything here. Ideally consolidate with transition-merging.
}
// Update local states (before animating).
setDividerVisibility(true);
setSplitsVisible(true);
addDividerBarToTransition(info, t, true /* show */);
// Make some noise if things aren't totally expected. These states shouldn't effect
// transitions locally, but remotes (like Launcher) may get confused if they were
// depending on listener callbacks. This can happen because task-organizer callbacks
// aren't serialized with transition callbacks.
// TODO(b/184679596): Find a way to either include task-org information in
// the transition, or synchronize task-org callbacks.
if (!mMainStage.containsTask(mainChild.getTaskInfo().taskId)) {
Log.w(TAG, "Expected onTaskAppeared on " + mMainStage
+ " to have been called with " + mainChild.getTaskInfo().taskId
+ " before startAnimation().");
}
if (!mSideStage.containsTask(sideChild.getTaskInfo().taskId)) {
Log.w(TAG, "Expected onTaskAppeared on " + mSideStage
+ " to have been called with " + sideChild.getTaskInfo().taskId
+ " before startAnimation().");
}
return true;
}
private boolean startPendingDismissAnimation(@NonNull IBinder transition,

View File

@@ -24,6 +24,7 @@ import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER;
import static com.android.wm.shell.splitscreen.SplitTestUtils.createMockSurface;
import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_SCREEN_PAIR_OPEN;
@@ -66,7 +67,6 @@ import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.common.split.SplitLayout;
import com.android.wm.shell.recents.RecentTasksController;
import com.android.wm.shell.transition.Transitions;
import org.junit.Before;
@@ -132,6 +132,40 @@ public class SplitTransitionTests extends ShellTestCase {
.setParentTaskId(mSideStage.mRootTaskInfo.taskId).build();
}
@Test
public void testLaunchToSide() {
ActivityManager.RunningTaskInfo newTask = new TestRunningTaskInfoBuilder()
.setParentTaskId(mSideStage.mRootTaskInfo.taskId).build();
ActivityManager.RunningTaskInfo reparentTask = new TestRunningTaskInfoBuilder()
.setParentTaskId(mMainStage.mRootTaskInfo.taskId).build();
// Create a request to start a new task in side stage
TransitionRequestInfo request = new TransitionRequestInfo(TRANSIT_TO_FRONT, newTask, null);
IBinder transition = mock(IBinder.class);
WindowContainerTransaction result =
mStageCoordinator.handleRequest(transition, request);
// it should handle the transition to enter split screen.
assertNotNull(result);
assertTrue(containsSplitEnter(result));
// simulate the transition
TransitionInfo.Change openChange = createChange(TRANSIT_OPEN, newTask);
TransitionInfo.Change reparentChange = createChange(TRANSIT_CHANGE, reparentTask);
TransitionInfo info = new TransitionInfo(TRANSIT_TO_FRONT, 0);
info.addChange(openChange);
info.addChange(reparentChange);
mSideStage.onTaskAppeared(newTask, createMockSurface());
mMainStage.onTaskAppeared(reparentTask, createMockSurface());
boolean accepted = mStageCoordinator.startAnimation(transition, info,
mock(SurfaceControl.Transaction.class),
mock(SurfaceControl.Transaction.class),
mock(Transitions.TransitionFinishCallback.class));
assertTrue(accepted);
assertTrue(mStageCoordinator.isSplitScreenVisible());
}
@Test
public void testLaunchPair() {
TransitionInfo info = createEnterPairInfo();
@@ -324,6 +358,22 @@ public class SplitTransitionTests extends ShellTestCase {
true /* includingTopTask */);
}
private boolean containsSplitEnter(@NonNull WindowContainerTransaction wct) {
boolean movedMainToFront = false;
boolean movedSideToFront = false;
for (int i = 0; i < wct.getHierarchyOps().size(); ++i) {
WindowContainerTransaction.HierarchyOp op = wct.getHierarchyOps().get(i);
if (op.getType() == HIERARCHY_OP_TYPE_REORDER) {
if (op.getContainer() == mMainStage.mRootTaskInfo.token.asBinder()) {
movedMainToFront = true;
} else if (op.getContainer() == mSideStage.mRootTaskInfo.token.asBinder()) {
movedSideToFront = true;
}
}
}
return movedMainToFront && movedSideToFront;
}
private boolean containsSplitExit(@NonNull WindowContainerTransaction wct) {
// reparenting of child tasks to null constitutes exiting split.
boolean reparentedMain = false;

View File

@@ -1672,7 +1672,8 @@ class ActivityStarter {
}
if (newTransition != null) {
transitionController.requestStartTransition(newTransition,
mTargetTask, remoteTransition, null /* displayChange */);
mTargetTask == null ? r.getTask() : mTargetTask,
remoteTransition, null /* displayChange */);
} else if (started) {
// Make the collecting transition wait until this request is ready.
transitionController.setReady(r, false);