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 46c4a40191be4..afd51171bdd71 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 @@ -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, 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 dd27017a37aaa..5e8f29f7cf18d 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 @@ -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, diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java index 1eae625233a0c..c853fd350986b 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java @@ -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; diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index 178e99d375a0a..65f9e83dc5df6 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -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);