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 0b941b59b3db7..9113c79d40f8f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java @@ -103,6 +103,8 @@ public final class ShellCommandHandlerImpl { return runMoveToSideStage(args, pw); case "removeFromSideStage": return runRemoveFromSideStage(args, pw); + case "setSideStageOutline": + return runSetSideStageOutline(args, pw); case "setSideStagePosition": return runSetSideStagePosition(args, pw); case "setSideStageVisibility": @@ -161,6 +163,18 @@ public final class ShellCommandHandlerImpl { return true; } + private boolean runSetSideStageOutline(String[] args, PrintWriter pw) { + if (args.length < 3) { + // First arguments are "WMShell" and command name. + pw.println("Error: whether to enable or disable side stage outline border should be" + + " provided as arguments"); + return false; + } + final boolean enable = new Boolean(args[2]); + mSplitScreenOptional.ifPresent(split -> split.setSideStageOutline(enable)); + return true; + } + private boolean runSetSideStagePosition(String[] args, PrintWriter pw) { if (args.length < 3) { // First arguments are "WMShell" and command name. @@ -175,7 +189,7 @@ public final class ShellCommandHandlerImpl { 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"); + pw.println("Error: side stage visibility should be provided as arguments"); return false; } final Boolean visible = new Boolean(args[2]); @@ -197,6 +211,8 @@ public final class ShellCommandHandlerImpl { 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(" setSideStageOutline "); + pw.println(" Enable/Disable outline on the side-stage."); pw.println(" setSideStagePosition "); pw.println(" Sets the position of the side-stage."); pw.println(" setSideStageVisibility "); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/OutlineManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/OutlineManager.java index f74cb03424d67..0b763f2d05f7a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/OutlineManager.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/OutlineManager.java @@ -22,7 +22,6 @@ import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMA import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; -import android.annotation.Nullable; import android.content.Context; import android.content.res.Configuration; import android.graphics.PixelFormat; @@ -39,8 +38,6 @@ import android.view.WindowlessWindowManager; import com.android.wm.shell.R; -import java.util.function.Supplier; - /** * Handles drawing outline of the bounds of provided root surface. The outline will be drown with * the consideration of display insets like status bar, navigation bar and display cutout. @@ -48,49 +45,27 @@ import java.util.function.Supplier; class OutlineManager extends WindowlessWindowManager { private static final String WINDOW_NAME = "SplitOutlineLayer"; private final Context mContext; - private final int mOutlineColor; private final Rect mOutlineBounds = new Rect(); private final Rect mTmpBounds = new Rect(); - private final Supplier mOutlineSurfaceSupplier; - private final SurfaceControlViewHost mViewHost; - private final SurfaceControl mLeash; + private SurfaceControlViewHost mViewHost; + private SurfaceControl mHostLeash; + private SurfaceControl mLeash; + private int mOutlineColor; - /** - * Constructs {@link #OutlineManager} with indicated outline color for the provided root - * surface. - */ - OutlineManager(Context context, Configuration configuration, - Supplier outlineSurfaceSupplier, int color) { + OutlineManager(Context context, Configuration configuration) { super(configuration, null /* rootSurface */, null /* hostInputToken */); mContext = context.createWindowContext(context.getDisplay(), TYPE_APPLICATION_OVERLAY, null /* options */); - mOutlineSurfaceSupplier = outlineSurfaceSupplier; - mOutlineColor = color; - - mViewHost = new SurfaceControlViewHost(mContext, mContext.getDisplay(), this); - final OutlineRoot rootView = (OutlineRoot) LayoutInflater.from(mContext) - .inflate(R.layout.split_outline, null); - rootView.updateOutlineBounds(mOutlineBounds, mOutlineColor); - - final WindowManager.LayoutParams lp = new WindowManager.LayoutParams( - 0 /* width */, 0 /* height */, TYPE_APPLICATION_OVERLAY, - FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCHABLE, PixelFormat.TRANSLUCENT); - lp.token = new Binder(); - lp.setTitle(WINDOW_NAME); - lp.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION | PRIVATE_FLAG_TRUSTED_OVERLAY; - // TODO(b/189839391): Set INPUT_FEATURE_NO_INPUT_CHANNEL after WM supports - // TRUSTED_OVERLAY for windowless window without input channel. - mViewHost.setView(rootView, lp); - - mLeash = getSurfaceControl(mViewHost.getWindowToken()); } @Override protected void attachToParentSurface(IWindow window, SurfaceControl.Builder b) { - b.setParent(mOutlineSurfaceSupplier.get()); + b.setParent(mHostLeash); } - boolean updateOutlineBounds(Rect rootBounds) { + boolean drawOutlineBounds(Rect rootBounds) { + if (mLeash == null || mViewHost == null) return false; + computeOutlineBounds(mContext, rootBounds, mTmpBounds); if (mOutlineBounds.equals(mTmpBounds)) { return false; @@ -107,9 +82,32 @@ class OutlineManager extends WindowlessWindowManager { return true; } - @Nullable - SurfaceControl getLeash() { - return mLeash; + void inflate(SurfaceControl.Transaction t, SurfaceControl hostLeash, int color) { + if (mLeash != null || mViewHost != null) return; + + mHostLeash = hostLeash; + mOutlineColor = color; + mViewHost = new SurfaceControlViewHost(mContext, mContext.getDisplay(), this); + final OutlineRoot rootView = (OutlineRoot) LayoutInflater.from(mContext) + .inflate(R.layout.split_outline, null); + + final WindowManager.LayoutParams lp = new WindowManager.LayoutParams( + 0 /* width */, 0 /* height */, TYPE_APPLICATION_OVERLAY, + FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCHABLE, PixelFormat.TRANSLUCENT); + lp.token = new Binder(); + lp.setTitle(WINDOW_NAME); + lp.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION | PRIVATE_FLAG_TRUSTED_OVERLAY; + // TODO(b/189839391): Set INPUT_FEATURE_NO_INPUT_CHANNEL after WM supports + // TRUSTED_OVERLAY for windowless window without input channel. + mViewHost.setView(rootView, lp); + mLeash = getSurfaceControl(mViewHost.getWindowToken()); + t.setLayer(mLeash, Integer.MAX_VALUE); + } + + void release() { + if (mViewHost != null) { + mViewHost.release(); + } } private static void computeOutlineBounds(Context context, Rect rootBounds, Rect outBounds) { 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 5d29535ca2973..2b19bb965fed0 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 @@ -21,12 +21,10 @@ import android.app.ActivityManager; import android.content.Context; import android.graphics.Color; import android.graphics.Rect; -import android.view.SurfaceControl; import android.view.SurfaceSession; import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; -import com.android.internal.annotations.VisibleForTesting; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.SyncTransactionQueue; @@ -48,20 +46,12 @@ class SideStage extends StageTaskListener { mContext = context; } - @VisibleForTesting - SideStage(Context context, ShellTaskOrganizer taskOrganizer, int displayId, - StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue, - SurfaceSession surfaceSession, OutlineManager outlineManager) { - this(context, taskOrganizer, displayId, callbacks, syncQueue, surfaceSession); - mOutlineManager = outlineManager; - } - void addTask(ActivityManager.RunningTaskInfo task, Rect rootBounds, WindowContainerTransaction wct) { final WindowContainerToken rootToken = mRootTaskInfo.token; wct.setBounds(rootToken, rootBounds) .reparent(task.token, rootToken, true /* onTop*/) - // Moving the root task to top after the child tasks were repareted , or the root + // Moving the root task to top after the child tasks were reparented , or the root // task cannot be visible and focused. .reorder(rootToken, true /* onTop */); } @@ -87,31 +77,33 @@ class SideStage extends StageTaskListener { return true; } - @Override - @CallSuper - public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) { - super.onTaskAppeared(taskInfo, leash); - if (mRootTaskInfo != null && mRootTaskInfo.taskId == taskInfo.taskId - && mOutlineManager == null) { - mOutlineManager = new OutlineManager(mContext, mRootTaskInfo.configuration, - () -> mRootLeash, - Color.YELLOW); - if (mOutlineManager.getLeash() != null) { - mSyncQueue.runInSync(t -> { - t.setLayer(mOutlineManager.getLeash(), Integer.MAX_VALUE); - }); + void enableOutline(boolean enable) { + if (enable) { + if (mOutlineManager == null && mRootTaskInfo != null) { + mOutlineManager = new OutlineManager(mContext, mRootTaskInfo.configuration); + mSyncQueue.runInSync(t -> mOutlineManager.inflate(t, mRootLeash, Color.YELLOW)); + updateOutlineBounds(); + } + } else { + if (mOutlineManager != null) { + mOutlineManager.release(); + mOutlineManager = null; } } } + private void updateOutlineBounds() { + if (mOutlineManager == null || mRootTaskInfo == null || !mRootTaskInfo.isVisible) return; + mOutlineManager.drawOutlineBounds( + mRootTaskInfo.configuration.windowConfiguration.getBounds()); + } + @Override @CallSuper public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) { super.onTaskInfoChanged(taskInfo); - if (mRootTaskInfo != null && mRootTaskInfo.taskId == taskInfo.taskId - && mRootTaskInfo.isRunning) { - mOutlineManager.updateOutlineBounds( - mRootTaskInfo.configuration.windowConfiguration.getBounds()); + if (mRootTaskInfo != null && mRootTaskInfo.taskId == taskInfo.taskId) { + updateOutlineBounds(); } } } 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 36b2777ec3c44..d60fa29d8f77c 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 @@ -142,6 +142,10 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, return mStageCoordinator.removeFromSideStage(taskId); } + public void setSideStageOutline(boolean enable) { + mStageCoordinator.setSideStageOutline(enable); + } + public void setSideStagePosition(@SplitPosition int sideStagePosition) { mStageCoordinator.setSideStagePosition(sideStagePosition); } 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 4e91193f65551..67b5ab390042a 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 @@ -232,6 +232,10 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, return result; } + void setSideStageOutline(boolean enable) { + mSideStage.enableOutline(enable); + } + /** Starts 2 tasks in one transition. */ void startTasks(int mainTaskId, @Nullable Bundle mainOptions, int sideTaskId, @Nullable Bundle sideOptions, @SplitPosition int sidePosition, 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 2aad38f33efbe..69ead3ac9cf95 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 @@ -53,7 +53,6 @@ public class SideStageTests extends ShellTestCase { @Mock private SyncTransactionQueue mSyncQueue; @Mock private ActivityManager.RunningTaskInfo mRootTask; @Mock private SurfaceControl mRootLeash; - @Mock private OutlineManager mOutlineManager; @Spy private WindowContainerTransaction mWct; private SurfaceSession mSurfaceSession = new SurfaceSession(); private SideStage mSideStage; @@ -63,7 +62,7 @@ public class SideStageTests extends ShellTestCase { MockitoAnnotations.initMocks(this); mRootTask = new TestRunningTaskInfoBuilder().build(); mSideStage = new SideStage(mContext, mTaskOrganizer, DEFAULT_DISPLAY, mCallbacks, - mSyncQueue, mSurfaceSession, mOutlineManager); + mSyncQueue, mSurfaceSession); mSideStage.onTaskAppeared(mRootTask, mRootLeash); } 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 44444ad255a3f..b6da8681d8503 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 @@ -82,7 +82,6 @@ public class SplitTransitionTests extends ShellTestCase { @Mock private TransactionPool mTransactionPool; @Mock private Transitions mTransitions; @Mock private SurfaceSession mSurfaceSession; - @Mock private OutlineManager mOutlineManager; private SplitLayout mSplitLayout; private MainStage mMainStage; private SideStage mSideStage; @@ -104,8 +103,7 @@ public class SplitTransitionTests extends ShellTestCase { StageTaskListener.StageListenerCallbacks.class), mSyncQueue, mSurfaceSession); mMainStage.onTaskAppeared(new TestRunningTaskInfoBuilder().build(), createMockSurface()); mSideStage = new SideStage(mContext, mTaskOrganizer, DEFAULT_DISPLAY, mock( - StageTaskListener.StageListenerCallbacks.class), mSyncQueue, mSurfaceSession, - mOutlineManager); + StageTaskListener.StageListenerCallbacks.class), mSyncQueue, mSurfaceSession); mSideStage.onTaskAppeared(new TestRunningTaskInfoBuilder().build(), createMockSurface()); mStageCoordinator = new SplitTestUtils.TestStageCoordinator(mContext, DEFAULT_DISPLAY, mSyncQueue, mRootTDAOrganizer, mTaskOrganizer, mMainStage, mSideStage,