From 8531eb85a418bfa08b65a93b705d50567980acf9 Mon Sep 17 00:00:00 2001 From: Wale Ogunwale Date: Sun, 10 Jan 2021 17:31:00 -0800 Subject: [PATCH] Additional interfaces for main/side stage split-screen model Add some more interfaces APIs for manipulating main/side stage split-screen. Also, renamed pinTask/unpinTask to moveToSideStage/removeFromSideStage for consistency. Test: Command line Bug: 176061049 Change-Id: Ic5b909c42e027e6f4809931c6dc92af1bf79b050 --- .../wm/shell/ShellCommandHandlerImpl.java | 58 ++++++++++++++---- .../wm/shell/splitscreen/SideStage.java | 6 +- .../wm/shell/splitscreen/SplitScreen.java | 39 +++++++++--- .../splitscreen/SplitScreenController.java | 28 +++++++-- .../shell/splitscreen/StageCoordinator.java | 59 ++++++++++++++----- .../shell/splitscreen/StageTaskListener.java | 4 ++ .../wm/shell/splitscreen/SideStageTests.java | 4 +- .../splitscreen/StageCoordinatorTests.java | 15 +++-- 8 files changed, 165 insertions(+), 48 deletions(-) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java index 9ad5ed86905d8..52648d915f2ce 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java @@ -16,6 +16,8 @@ package com.android.wm.shell; +import static com.android.wm.shell.splitscreen.SplitScreen.SIDE_STAGE_POSITION_BOTTOM_OR_RIGHT; + import com.android.wm.shell.apppairs.AppPairs; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout; @@ -107,10 +109,14 @@ public final class ShellCommandHandlerImpl { return runPair(args, pw); case "unpair": return runUnpair(args, pw); - case "pinTask": - return runPinTask(args, pw); - case "unpinTask": - return runUnpinTask(args, pw); + case "moveToSideStage": + return runMoveToSideStage(args, pw); + case "removeFromSideStage": + return runRemoveFromSideStage(args, pw); + case "setSideStagePosition": + return runSetSideStagePosition(args, pw); + case "setSideStageVisibility": + return runSetSideStageVisibility(args, pw); case "help": return runHelp(pw); default: @@ -141,25 +147,50 @@ public final class ShellCommandHandlerImpl { return true; } - private boolean runPinTask(String[] args, PrintWriter pw) { + private boolean runMoveToSideStage(String[] args, PrintWriter pw) { if (args.length < 3) { // First arguments are "WMShell" and command name. pw.println("Error: task id should be provided as arguments"); return false; } final int taskId = new Integer(args[2]); - mSplitScreenOptional.ifPresent(split -> split.pinTask(taskId)); + final int sideStagePosition = args.length > 3 + ? new Integer(args[3]) : SIDE_STAGE_POSITION_BOTTOM_OR_RIGHT; + mSplitScreenOptional.ifPresent(split -> split.moveToSideStage(taskId, sideStagePosition)); return true; } - private boolean runUnpinTask(String[] args, PrintWriter pw) { + private boolean runRemoveFromSideStage(String[] args, PrintWriter pw) { if (args.length < 3) { // First arguments are "WMShell" and command name. pw.println("Error: task id should be provided as arguments"); return false; } final int taskId = new Integer(args[2]); - mSplitScreenOptional.ifPresent(split -> split.unpinTask(taskId)); + mSplitScreenOptional.ifPresent(split -> split.removeFromSideStage(taskId)); + return true; + } + + private boolean runSetSideStagePosition(String[] args, PrintWriter pw) { + if (args.length < 3) { + // First arguments are "WMShell" and command name. + pw.println("Error: side stage position should be provided as arguments"); + return false; + } + final int position = new Integer(args[2]); + mSplitScreenOptional.ifPresent(split -> split.setSideStagePosition(position)); + return true; + } + + private boolean runSetSideStageVisibility(String[] args, PrintWriter pw) { + if (args.length < 3) { + // First arguments are "WMShell" and command name. + pw.println("Error: side stage position should be provided as arguments"); + return false; + } + final Boolean visible = new Boolean(args[2]); + + mSplitScreenOptional.ifPresent(split -> split.setSideStageVisibility(visible)); return true; } @@ -172,9 +203,14 @@ public final class ShellCommandHandlerImpl { pw.println(" pair "); pw.println(" unpair "); pw.println(" Pairs/unpairs tasks with given ids."); - pw.println(" pinTask "); - pw.println(" unpinTask "); - pw.println(" Pin/Unpin a task with given id in split-screen mode."); + pw.println(" moveToSideStage "); + pw.println(" Move a task with given id in split-screen mode."); + pw.println(" removeFromSideStage "); + pw.println(" Remove a task with given id in split-screen mode."); + pw.println(" setSideStagePosition "); + pw.println(" Sets the position of the side-stage."); + pw.println(" setSideStageVisibility "); + pw.println(" Show/hide side-stage."); return true; } 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 2c26003fcb9da..5645c19d5c464 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 @@ -37,7 +37,7 @@ class SideStage extends StageTaskListener { super(taskOrganizer, displayId, callbacks, syncQueue); } - public void addTask(ActivityManager.RunningTaskInfo task, Rect rootBounds, + void addTask(ActivityManager.RunningTaskInfo task, Rect rootBounds, WindowContainerTransaction wct) { final WindowContainerToken rootToken = mRootTaskInfo.token; wct.setHidden(rootToken, false) @@ -48,13 +48,13 @@ class SideStage extends StageTaskListener { .reorder(rootToken, true); } - public boolean removeTask(int taskId, WindowContainerTransaction wct) { + boolean removeTask(int taskId, WindowContainerToken newParent, WindowContainerTransaction wct) { final ActivityManager.RunningTaskInfo task = mChildrenTaskInfo.get(taskId); if (task == null) return false; wct.setHidden(mRootTaskInfo.token, true) .reorder(mRootTaskInfo.token, false) - .reparent(task.token, null, false /* onTop */); + .reparent(task.token, newParent, false /* onTop */); return true; } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java index 152c40eedc946..08c2856d67924 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java @@ -16,6 +16,7 @@ package com.android.wm.shell.splitscreen; +import android.annotation.IntDef; import android.app.ActivityManager; import androidx.annotation.NonNull; @@ -29,13 +30,37 @@ import java.io.PrintWriter; */ @ExternalThread public interface SplitScreen { - /** Unpin a task in the side-stage of split-screen. */ - boolean pinTask(int taskId); - /** Unpin a task in the side-stage of split-screen. */ - boolean pinTask(ActivityManager.RunningTaskInfo task); - /** Unpin a task from the side-stage of split-screen. */ - boolean unpinTask(int taskId); -// TODO: Do we need show/hide side stage or is startActivity and sendToBack good enough? + /** + * Specifies that the side-stage is positioned at the top half of the screen if + * in portrait mode or at the left half of the screen if in landscape mode. + */ + int SIDE_STAGE_POSITION_TOP_OR_LEFT = 0; + + /** + * Specifies that the side-stage is positioned at the bottom half of the screen if + * in portrait mode or at the right half of the screen if in landscape mode. + */ + int SIDE_STAGE_POSITION_BOTTOM_OR_RIGHT = 1; + + @IntDef(prefix = { "SIDE_STAGE_POSITION_" }, value = { + SIDE_STAGE_POSITION_TOP_OR_LEFT, + SIDE_STAGE_POSITION_BOTTOM_OR_RIGHT + }) + @interface SideStagePosition {} + + /** @return {@code true} if split-screen is currently visible. */ + boolean isSplitScreenVisible(); + /** Moves a task in the side-stage of split-screen. */ + boolean moveToSideStage(int taskId, @SideStagePosition int sideStagePosition); + /** Moves a task in the side-stage of split-screen. */ + boolean moveToSideStage(ActivityManager.RunningTaskInfo task, + @SideStagePosition int sideStagePosition); + /** Removes a task from the side-stage of split-screen. */ + boolean removeFromSideStage(int taskId); + /** Sets the position of the side-stage. */ + void setSideStagePosition(@SideStagePosition int sideStagePosition); + /** Hides the side-stage if it is currently visible. */ + void setSideStageVisibility(boolean visible); /** Dumps current status of split-screen. */ void dump(@NonNull PrintWriter pw, String prefix); /** Called when the shell organizer has been registered. */ 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 3183f762263c0..55cfea5b6da3b 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 @@ -62,19 +62,35 @@ public class SplitScreenController implements SplitScreen { } @Override - public boolean pinTask(int taskId) { + public boolean isSplitScreenVisible() { + return mStageCoordinator.isSplitScreenVisible(); + } + + @Override + public boolean moveToSideStage(int taskId, @SideStagePosition int sideStagePosition) { final ActivityManager.RunningTaskInfo task = mTaskOrganizer.getRunningTaskInfo(taskId); - return task != null && pinTask(task); + return task != null && moveToSideStage(task, sideStagePosition); } @Override - public boolean pinTask(ActivityManager.RunningTaskInfo task) { - return mStageCoordinator.pinTask(task); + public boolean moveToSideStage(ActivityManager.RunningTaskInfo task, + @SideStagePosition int sideStagePosition) { + return mStageCoordinator.moveToSideStage(task, sideStagePosition); } @Override - public boolean unpinTask(int taskId) { - return mStageCoordinator.unpinTask(taskId); + public boolean removeFromSideStage(int taskId) { + return mStageCoordinator.removeFromSideStage(taskId); + } + + @Override + public void setSideStagePosition(@SideStagePosition int sideStagePosition) { + mStageCoordinator.setSideStagePosition(sideStagePosition); + } + + @Override + public void setSideStageVisibility(boolean visible) { + mStageCoordinator.setSideStageVisibility(visible); } @Override 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 bed033b851e21..0c6edf10ba944 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 @@ -19,6 +19,9 @@ package com.android.wm.shell.splitscreen; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; +import static com.android.wm.shell.splitscreen.SplitScreen.SIDE_STAGE_POSITION_BOTTOM_OR_RIGHT; +import static com.android.wm.shell.splitscreen.SplitScreen.SIDE_STAGE_POSITION_TOP_OR_LEFT; + import android.app.ActivityManager; import android.content.Context; import android.graphics.Rect; @@ -58,6 +61,8 @@ class StageCoordinator implements SplitLayout.LayoutChangeListener, private final StageListenerImpl mMainStageListener = new StageListenerImpl(); private final SideStage mSideStage; private final StageListenerImpl mSideStageListener = new StageListenerImpl(); + private @SplitScreen.SideStagePosition int mSideStagePosition = + SIDE_STAGE_POSITION_BOTTOM_OR_RIGHT; private final int mDisplayId; private SplitLayout mSplitLayout; @@ -94,27 +99,49 @@ class StageCoordinator implements SplitLayout.LayoutChangeListener, mRootTDAOrganizer.registerListener(displayId, this); } - boolean pinTask(ActivityManager.RunningTaskInfo task) { - final WindowContainerTransaction wct = new WindowContainerTransaction(); + boolean isSplitScreenVisible() { + return mSideStageListener.mVisible && mMainStageListener.mVisible; + } + boolean moveToSideStage(ActivityManager.RunningTaskInfo task, + @SplitScreen.SideStagePosition int sideStagePosition) { + final WindowContainerTransaction wct = new WindowContainerTransaction(); + mSideStagePosition = sideStagePosition; mMainStage.activate(getMainStageBounds(), wct); mSideStage.addTask(task, getSideStageBounds(), wct); mTaskOrganizer.applyTransaction(wct); return true; } - boolean unpinTask(int taskId) { + boolean removeFromSideStage(int taskId) { final WindowContainerTransaction wct = new WindowContainerTransaction(); /** * {@link MainStage} will be deactivated in {@link #onStageHasChildrenChanged} if the * {@link SideStage} no longer has children. */ - final boolean result = mSideStage.removeTask(taskId, wct); + final boolean result = mSideStage.removeTask(taskId, + mMainStage.isActive() ? mMainStage.mRootTaskInfo.token : null, + wct); mTaskOrganizer.applyTransaction(wct); return result; } + void setSideStagePosition(@SplitScreen.SideStagePosition int sideStagePosition) { + mSideStagePosition = sideStagePosition; + if (mSideStageListener.mVisible) { + onStageVisibilityChanged(mSideStageListener); + } + } + + void setSideStageVisibility(boolean visible) { + if (!mSideStageListener.mVisible == visible) return; + + final WindowContainerTransaction wct = new WindowContainerTransaction(); + mSideStage.setVisibility(visible, wct); + mTaskOrganizer.applyTransaction(wct); + } + private void onStageRootTaskVanished(StageListenerImpl stageListener) { if (stageListener == mMainStageListener || stageListener == mSideStageListener) { final WindowContainerTransaction wct = new WindowContainerTransaction(); @@ -171,6 +198,7 @@ class StageCoordinator implements SplitLayout.LayoutChangeListener, t.hide(dividerLeash); } } + if (sideStageVisible) { final Rect sideStageBounds = getSideStageBounds(); t.show(sideStageLeash) @@ -181,17 +209,18 @@ class StageCoordinator implements SplitLayout.LayoutChangeListener, } else { t.hide(sideStageLeash); } + if (mainStageVisible) { final Rect mainStageBounds = getMainStageBounds(); - t.show(mainStageLeash) - .setPosition(mainStageLeash, - mainStageBounds.left, mainStageBounds.top); + t.show(mainStageLeash); if (sideStageVisible) { - t.setWindowCrop(mainStageLeash, - mainStageBounds.width(), mainStageBounds.height()); + t.setPosition(mainStageLeash, mainStageBounds.left, mainStageBounds.top) + .setWindowCrop(mainStageLeash, + mainStageBounds.width(), mainStageBounds.height()); } else { - // Clear window crop if side stage isn't visible. - t.setWindowCrop(mainStageLeash, null); + // Clear window crop and position if side stage isn't visible. + t.setPosition(mainStageLeash, 0, 0) + .setWindowCrop(mainStageLeash, null); } } else { t.hide(mainStageLeash); @@ -204,7 +233,7 @@ class StageCoordinator implements SplitLayout.LayoutChangeListener, final WindowContainerTransaction wct = new WindowContainerTransaction(); if (mSideStageListener.mHasChildren) { // Make sure the main stage is active. - mMainStage.activate(mSplitLayout.getBounds1(), wct); + mMainStage.activate(getMainStageBounds(), wct); } else { // The side stage no long has children so we can deactivate the main stage. mMainStage.deactivate(wct); @@ -291,11 +320,13 @@ class StageCoordinator implements SplitLayout.LayoutChangeListener, } private Rect getSideStageBounds() { - return mSplitLayout.getBounds2(); + return mSideStagePosition == SIDE_STAGE_POSITION_TOP_OR_LEFT + ? mSplitLayout.getBounds1() : mSplitLayout.getBounds2(); } private Rect getMainStageBounds() { - return mSplitLayout.getBounds1(); + return mSideStagePosition == SIDE_STAGE_POSITION_TOP_OR_LEFT + ? mSplitLayout.getBounds2() : mSplitLayout.getBounds1(); } @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 de51623618509..30f2701151d7d 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 @@ -118,6 +118,10 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener { wct.setBounds(mRootTaskInfo.token, bounds); } + void setVisibility(boolean visible, WindowContainerTransaction wct) { + wct.reorder(mRootTaskInfo.token, visible /* onTop */); + } + private void updateChildTaskSurface(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash, boolean firstAppeared) { final Point taskPositionInParent = taskInfo.positionInParent; diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SideStageTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SideStageTests.java index 25bf9c6c947e7..01888b758bf6f 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SideStageTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SideStageTests.java @@ -74,10 +74,10 @@ public class SideStageTests { @Test public void testRemoveTask() { final ActivityManager.RunningTaskInfo task = new TestRunningTaskInfoBuilder().build(); - assertThat(mSideStage.removeTask(task.taskId, mWct)).isFalse(); + assertThat(mSideStage.removeTask(task.taskId, null, mWct)).isFalse(); mSideStage.mChildrenTaskInfo.put(task.taskId, task); - assertThat(mSideStage.removeTask(task.taskId, mWct)).isTrue(); + assertThat(mSideStage.removeTask(task.taskId, null, mWct)).isTrue(); verify(mWct).reparent(eq(task.token), isNull(), eq(false)); } } 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 e3c36469587b7..168e0df35fe18 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 @@ -19,8 +19,11 @@ package com.android.wm.shell.splitscreen; import static android.view.Display.DEFAULT_DISPLAY; import static android.window.DisplayAreaOrganizer.FEATURE_DEFAULT_TASK_CONTAINER; +import static com.android.wm.shell.splitscreen.SplitScreen.SIDE_STAGE_POSITION_BOTTOM_OR_RIGHT; + import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.verify; import android.app.ActivityManager; @@ -65,10 +68,10 @@ public class StageCoordinatorTests extends ShellTestCase { } @Test - public void testPinTask() { + public void testMoveToSideStage() { final ActivityManager.RunningTaskInfo task = new TestRunningTaskInfoBuilder().build(); - mStageCoordinator.pinTask(task); + mStageCoordinator.moveToSideStage(task, SIDE_STAGE_POSITION_BOTTOM_OR_RIGHT); verify(mMainStage).activate(any(Rect.class), any(WindowContainerTransaction.class)); verify(mSideStage).addTask(eq(task), any(Rect.class), @@ -76,12 +79,14 @@ public class StageCoordinatorTests extends ShellTestCase { } @Test - public void testUnpinTask() { + public void testRemoveFromSideStage() { final ActivityManager.RunningTaskInfo task = new TestRunningTaskInfoBuilder().build(); - mStageCoordinator.unpinTask(task.taskId); + doReturn(false).when(mMainStage).isActive(); + mStageCoordinator.removeFromSideStage(task.taskId); - verify(mSideStage).removeTask(eq(task.taskId), any(WindowContainerTransaction.class)); + verify(mSideStage).removeTask( + eq(task.taskId), any(), any(WindowContainerTransaction.class)); } private static class TestStageCoordinator extends StageCoordinator {