diff options
| author | 2020-11-17 22:45:44 +0000 | |
|---|---|---|
| committer | 2020-11-17 22:45:44 +0000 | |
| commit | 2b54b5d7091f65a879de4c873eee9f60d7382b07 (patch) | |
| tree | 67973a446f53c087a8126750d6e6e00037981f70 | |
| parent | 687f5e163f24ff31f822f986fd7a99b8832b6286 (diff) | |
| parent | 49fc5569f0316799ca2bf03a37b6eedb43fda24e (diff) | |
Merge "Make positioning in letterbox mode configurable."
23 files changed, 1173 insertions, 366 deletions
diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java index 5dfab6efe490..7fdf06f7233c 100644 --- a/core/java/android/app/TaskInfo.java +++ b/core/java/android/app/TaskInfo.java @@ -213,6 +213,13 @@ public class TaskInfo { */ public int parentTaskId; + + /** + * Parent bounds. + * @hide + */ + public Rect parentBounds; + TaskInfo() { // Do nothing } @@ -311,6 +318,7 @@ public class TaskInfo { letterboxActivityBounds = source.readTypedObject(Rect.CREATOR); positionInParent = source.readTypedObject(Point.CREATOR); parentTaskId = source.readInt(); + parentBounds = source.readTypedObject(Rect.CREATOR); } /** @@ -345,6 +353,7 @@ public class TaskInfo { dest.writeTypedObject(letterboxActivityBounds, flags); dest.writeTypedObject(positionInParent, flags); dest.writeInt(parentTaskId); + dest.writeTypedObject(parentBounds, flags); } @Override @@ -368,6 +377,7 @@ public class TaskInfo { + " letterboxActivityBounds=" + letterboxActivityBounds + " positionInParent=" + positionInParent + " parentTaskId: " + parentTaskId + + " parentBounds: " + parentBounds + "}"; } } diff --git a/libs/WindowManager/Shell/res/raw/wm_shell_protolog.json b/libs/WindowManager/Shell/res/raw/wm_shell_protolog.json index 8213f2fec04d..db9e1af9ec1e 100644 --- a/libs/WindowManager/Shell/res/raw/wm_shell_protolog.json +++ b/libs/WindowManager/Shell/res/raw/wm_shell_protolog.json @@ -1,6 +1,12 @@ { "version": "1.0.0", "messages": { + "-1993693214": { + "message": "Letterbox Task Changed: #%d", + "level": "VERBOSE", + "group": "WM_SHELL_TASK_ORG", + "at": "com\/android\/wm\/shell\/letterbox\/LetterboxTaskListener.java" + }, "-1683614271": { "message": "Existing task: id=%d component=%s", "level": "VERBOSE", @@ -67,12 +73,6 @@ "group": "WM_SHELL_TASK_ORG", "at": "com\/android\/wm\/shell\/ShellTaskOrganizer.java" }, - "-848099324": { - "message": "Letterbox Task Appeared: #%d", - "level": "VERBOSE", - "group": "WM_SHELL_TASK_ORG", - "at": "com\/android\/wm\/shell\/LetterboxTaskListener.java" - }, "-842742255": { "message": "%s onTaskAppeared unknown taskId=%d winMode=%d", "level": "VERBOSE", @@ -97,6 +97,12 @@ "group": "WM_SHELL_TASK_ORG", "at": "com\/android\/wm\/shell\/splitscreen\/SplitScreenTaskListener.java" }, + "-342975160": { + "message": "Letterbox Task Vanished: #%d", + "level": "VERBOSE", + "group": "WM_SHELL_TASK_ORG", + "at": "com\/android\/wm\/shell\/letterbox\/LetterboxTaskListener.java" + }, "-234284913": { "message": "unpair taskId=%d pair=%s", "level": "VERBOSE", @@ -175,24 +181,12 @@ "group": "WM_SHELL_TASK_ORG", "at": "com\/android\/wm\/shell\/apppairs\/AppPairsPool.java" }, - "1104702476": { - "message": "Letterbox Task Changed: #%d", - "level": "VERBOSE", - "group": "WM_SHELL_TASK_ORG", - "at": "com\/android\/wm\/shell\/LetterboxTaskListener.java" - }, "1184615936": { "message": "Set drop target window visibility: displayId=%d visibility=%d", "level": "VERBOSE", "group": "WM_SHELL_DRAG_AND_DROP", "at": "com\/android\/wm\/shell\/draganddrop\/DragAndDropController.java" }, - "1218010718": { - "message": "Letterbox Task Vanished: #%d", - "level": "VERBOSE", - "group": "WM_SHELL_TASK_ORG", - "at": "com\/android\/wm\/shell\/LetterboxTaskListener.java" - }, "1481772149": { "message": "Current target: %s", "level": "VERBOSE", @@ -205,6 +199,12 @@ "group": "WM_SHELL_DRAG_AND_DROP", "at": "com\/android\/wm\/shell\/draganddrop\/DragAndDropController.java" }, + "1885882094": { + "message": "Letterbox Task Appeared: #%d", + "level": "VERBOSE", + "group": "WM_SHELL_TASK_ORG", + "at": "com\/android\/wm\/shell\/letterbox\/LetterboxTaskListener.java" + }, "1891981945": { "message": "release entry.taskId=%s listener=%s size=%d", "level": "VERBOSE", diff --git a/libs/WindowManager/Shell/res/values/config.xml b/libs/WindowManager/Shell/res/values/config.xml index e99350b264b9..c913e0c441e5 100644 --- a/libs/WindowManager/Shell/res/values/config.xml +++ b/libs/WindowManager/Shell/res/values/config.xml @@ -36,4 +36,16 @@ <!-- Bounds [left top right bottom] on screen for picture-in-picture (PIP) windows, when the PIP menu is shown in center. --> <string translatable="false" name="pip_menu_bounds">"596 280 1324 690"</string> + + <!-- Gravity of letterboxed apps in portrait screen orientation. + Can be Gravity.TOP, Gravity.CENTER or Gravity.BOTTOM. + Any other value will result in runtime exception for a letterboxed activity. + Default is Gravity.TOP. --> + <integer name="config_letterboxPortraitGravity">0x00000030</integer> + + <!-- Gravity of letterboxed apps in landscape screen orientation. + Can be Gravity.LEFT, Gravity.CENTER or Gravity.RIGHT. + Any other value will result in runtime exception for a letterboxed activity. + Default is Gravity.CENTER. --> + <integer name="config_letterboxLandscapeGravity">0x00000011</integer> </resources> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/FullscreenTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/FullscreenTaskListener.java index fc0a76e8d286..4f13b83bc29d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/FullscreenTaskListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/FullscreenTaskListener.java @@ -32,14 +32,17 @@ import com.android.wm.shell.protolog.ShellProtoLogGroup; import java.io.PrintWriter; -class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener { +/** + * Organizes tasks presented in {@link android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN}. + */ +public class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener { private static final String TAG = "FullscreenTaskListener"; private final SyncTransactionQueue mSyncQueue; private final ArraySet<Integer> mTasks = new ArraySet<>(); - FullscreenTaskListener(SyncTransactionQueue syncQueue) { + public FullscreenTaskListener(SyncTransactionQueue syncQueue) { mSyncQueue = syncQueue; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/LetterboxTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/LetterboxTaskListener.java deleted file mode 100644 index 9010c2088c34..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/LetterboxTaskListener.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell; - -import android.app.ActivityManager; -import android.graphics.Point; -import android.graphics.Rect; -import android.util.Slog; -import android.util.SparseArray; -import android.view.SurfaceControl; - -import com.android.internal.protolog.common.ProtoLog; -import com.android.wm.shell.common.SyncTransactionQueue; -import com.android.wm.shell.protolog.ShellProtoLogGroup; - -/** - * Organizes a task in {@link android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN} when - * it's presented in the letterbox mode either because orientations of a top activity and a device - * don't match or because a top activity is in a size compat mode. - */ -final class LetterboxTaskListener implements ShellTaskOrganizer.TaskListener { - private static final String TAG = "LetterboxTaskListener"; - - private final SyncTransactionQueue mSyncQueue; - - private final SparseArray<SurfaceControl> mLeashByTaskId = new SparseArray<>(); - - LetterboxTaskListener(SyncTransactionQueue syncQueue) { - mSyncQueue = syncQueue; - } - - @Override - public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) { - synchronized (mLeashByTaskId) { - if (mLeashByTaskId.get(taskInfo.taskId) != null) { - throw new RuntimeException("Task appeared more than once: #" + taskInfo.taskId); - } - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Letterbox Task Appeared: #%d", - taskInfo.taskId); - mLeashByTaskId.put(taskInfo.taskId, leash); - final Rect taskBounds = taskInfo.getConfiguration().windowConfiguration.getBounds(); - final Rect activtyBounds = taskInfo.letterboxActivityBounds; - final Point taskPositionInParent = taskInfo.positionInParent; - mSyncQueue.runInSync(t -> { - setPositionAndWindowCrop( - t, leash, activtyBounds, taskBounds, taskPositionInParent); - if (!Transitions.ENABLE_SHELL_TRANSITIONS) { - t.setAlpha(leash, 1f); - t.setMatrix(leash, 1, 0, 0, 1); - t.show(leash); - } - }); - } - } - - @Override - public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) { - synchronized (mLeashByTaskId) { - if (mLeashByTaskId.get(taskInfo.taskId) == null) { - Slog.e(TAG, "Task already vanished: #" + taskInfo.taskId); - return; - } - mLeashByTaskId.remove(taskInfo.taskId); - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Letterbox Task Vanished: #%d", - taskInfo.taskId); - } - } - - @Override - public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) { - synchronized (mLeashByTaskId) { - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Letterbox Task Changed: #%d", - taskInfo.taskId); - final SurfaceControl leash = mLeashByTaskId.get(taskInfo.taskId); - final Rect taskBounds = taskInfo.getConfiguration().windowConfiguration.getBounds(); - final Rect activtyBounds = taskInfo.letterboxActivityBounds; - final Point taskPositionInParent = taskInfo.positionInParent; - mSyncQueue.runInSync(t -> { - setPositionAndWindowCrop( - t, leash, activtyBounds, taskBounds, taskPositionInParent); - }); - } - } - - private static void setPositionAndWindowCrop( - SurfaceControl.Transaction transaction, - SurfaceControl leash, - final Rect activityBounds, - final Rect taskBounds, - final Point taskPositionInParent) { - Rect activtyInTaskCoordinates = new Rect(activityBounds); - activtyInTaskCoordinates.offset(-taskBounds.left, -taskBounds.top); - transaction.setPosition(leash, taskPositionInParent.x, taskPositionInParent.y); - transaction.setWindowCrop(leash, activtyInTaskCoordinates); - } -} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandler.java new file mode 100644 index 000000000000..ada7e1a9e3ab --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandler.java @@ -0,0 +1,231 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell; + +import android.view.Gravity; + +import com.android.wm.shell.apppairs.AppPairs; +import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout; +import com.android.wm.shell.letterbox.LetterboxConfigController; +import com.android.wm.shell.onehanded.OneHanded; +import com.android.wm.shell.pip.Pip; +import com.android.wm.shell.splitscreen.SplitScreen; + +import java.io.PrintWriter; +import java.util.Optional; + +/** + * An entry point into the shell for dumping shell internal state and running adb commands. + * + * Use with {@code adb shell dumpsys activity service SystemUIService WMShell ...}. + */ +public final class ShellCommandHandler { + + private final Optional<SplitScreen> mSplitScreenOptional; + private final Optional<Pip> mPipOptional; + private final Optional<OneHanded> mOneHandedOptional; + private final Optional<HideDisplayCutout> mHideDisplayCutout; + private final ShellTaskOrganizer mShellTaskOrganizer; + private final Optional<AppPairs> mAppPairsOptional; + private final LetterboxConfigController mLetterboxConfigController; + + public ShellCommandHandler( + ShellTaskOrganizer shellTaskOrganizer, + Optional<SplitScreen> splitScreenOptional, + Optional<Pip> pipOptional, + Optional<OneHanded> oneHandedOptional, + Optional<HideDisplayCutout> hideDisplayCutout, + Optional<AppPairs> appPairsOptional, + LetterboxConfigController letterboxConfigController) { + mShellTaskOrganizer = shellTaskOrganizer; + mSplitScreenOptional = splitScreenOptional; + mPipOptional = pipOptional; + mOneHandedOptional = oneHandedOptional; + mHideDisplayCutout = hideDisplayCutout; + mAppPairsOptional = appPairsOptional; + mLetterboxConfigController = letterboxConfigController; + } + + /** Dumps WM Shell internal state. */ + public void dump(PrintWriter pw) { + mShellTaskOrganizer.dump(pw, ""); + pw.println(); + pw.println(); + mPipOptional.ifPresent(pip -> pip.dump(pw)); + mSplitScreenOptional.ifPresent(splitScreen -> splitScreen.dump(pw)); + mOneHandedOptional.ifPresent(oneHanded -> oneHanded.dump(pw)); + mHideDisplayCutout.ifPresent(hideDisplayCutout -> hideDisplayCutout.dump(pw)); + pw.println(); + pw.println(); + mAppPairsOptional.ifPresent(appPairs -> appPairs.dump(pw, "")); + } + + + /** Returns {@code true} if command was found and executed. */ + public boolean handleCommand(String[] args, PrintWriter pw) { + if (args.length < 2) { + // Argument at position 0 is "WMShell". + return false; + } + switch (args[1]) { + case "set-letterbox-portrait-gravity": + return runSetLetterboxPortraitGravity(args, pw); + case "get-letterbox-portrait-gravity": + return runGetLetterboxPortraitGravity(pw); + case "set-letterbox-landscape-gravity": + return runSetLetterboxLandscapeGravity(args, pw); + case "get-letterbox-landscape-gravity": + return runGetLetterboxLandscapeGravity(pw); + case "pair": + return runPair(args, pw); + case "unpair": + return runUnpair(args, pw); + case "help": + return runHelp(pw); + default: + return false; + } + } + + private boolean runSetLetterboxPortraitGravity(String[] args, PrintWriter pw) { + if (args.length < 3) { + // First two arguments are "WMShell" and command name. + pw.println("Error: reset, TOP, CENTER or BOTTOM should be provided as an argument"); + return true; + } + switch (args[2]) { + case "reset": + mLetterboxConfigController.resetPortraitGravity(); + break; + case "TOP": + mLetterboxConfigController.setPortraitGravity(Gravity.TOP); + break; + case "CENTER": + mLetterboxConfigController.setPortraitGravity(Gravity.CENTER); + break; + case "BOTTOM": + mLetterboxConfigController.setPortraitGravity(Gravity.BOTTOM); + break; + default: + pw.println("Error: expected reset, TOP, CENTER or BOTTOM but got " + args[2]); + } + return true; + } + + private boolean runGetLetterboxPortraitGravity(PrintWriter pw) { + final int gravity = mLetterboxConfigController.getPortraitGravity(); + switch (gravity) { + case Gravity.TOP: + pw.println("TOP"); + break; + case Gravity.CENTER: + pw.println("CENTER"); + break; + case Gravity.BOTTOM: + pw.println("BOTTOM"); + break; + default: + throw new AssertionError("Unexpected gravity: " + gravity); + } + return true; + } + + private boolean runSetLetterboxLandscapeGravity(String[] args, PrintWriter pw) { + if (args.length < 3) { + // First two arguments are "WMShell" and command name. + pw.println("Error: reset, LEFT, CENTER or RIGHT should be provided as an argument"); + return false; + } + switch (args[2]) { + case "reset": + mLetterboxConfigController.resetLandscapeGravity(); + break; + case "LEFT": + mLetterboxConfigController.setLandscapeGravity(Gravity.LEFT); + break; + case "CENTER": + mLetterboxConfigController.setLandscapeGravity(Gravity.CENTER); + break; + case "RIGHT": + mLetterboxConfigController.setLandscapeGravity(Gravity.RIGHT); + break; + default: + pw.println( + "Error: expected reset, LEFT, CENTER or RIGHT but got " + args[2]); + } + return true; + } + + private boolean runGetLetterboxLandscapeGravity(PrintWriter pw) { + final int gravity = mLetterboxConfigController.getLandscapeGravity(); + switch (gravity) { + case Gravity.LEFT: + pw.println("LEFT"); + break; + case Gravity.CENTER: + pw.println("CENTER"); + break; + case Gravity.RIGHT: + pw.println("RIGHT"); + break; + default: + throw new AssertionError("Unexpected gravity: " + gravity); + } + return true; + } + + private boolean runPair(String[] args, PrintWriter pw) { + if (args.length < 4) { + // First two arguments are "WMShell" and command name. + pw.println("Error: two task ids should be provided as arguments"); + return false; + } + final int taskId1 = new Integer(args[2]); + final int taskId2 = new Integer(args[3]); + mAppPairsOptional.ifPresent(appPairs -> appPairs.pair(taskId1, taskId2)); + return true; + } + + private boolean runUnpair(String[] args, PrintWriter pw) { + if (args.length < 3) { + // First two arguments are "WMShell" and command name. + pw.println("Error: task id should be provided as an argument"); + return false; + } + final int taskId = new Integer(args[2]); + mAppPairsOptional.ifPresent(appPairs -> appPairs.unpair(taskId)); + return true; + } + + private boolean runHelp(PrintWriter pw) { + pw.println("Window Manager Shell commands:"); + pw.println(" help"); + pw.println(" Print this help text."); + pw.println(" <no arguments provided>"); + pw.println(" Dump Window Manager Shell internal state"); + pw.println(" set-letterbox-portrait-gravity [reset|TOP|CENTER|BOTTOM]"); + pw.println(" get-letterbox-portrait-gravity"); + pw.println(" Set, reset or print letterbox gravity for portrait screen mode."); + pw.println(" set-letterbox-landscape-gravity [reset|LEFT|CENTER|RIGHT]"); + pw.println(" get-letterbox-landscape-gravity"); + pw.println(" Set, reset or print letterbox gravity for landscape screen mode."); + pw.println(" pair <taskId1> <taskId2>"); + pw.println(" unpair <taskId>"); + pw.println(" Pairs/unpairs tasks with given ids."); + return true; + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellDump.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellDump.java deleted file mode 100644 index 13089affd058..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellDump.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell; - -import com.android.wm.shell.apppairs.AppPairs; -import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout; -import com.android.wm.shell.onehanded.OneHanded; -import com.android.wm.shell.pip.Pip; -import com.android.wm.shell.splitscreen.SplitScreen; - -import java.io.PrintWriter; -import java.util.Optional; - -/** - * An entry point into the shell for dumping shell internal state. - */ -public class ShellDump { - - private final Optional<SplitScreen> mSplitScreenOptional; - private final Optional<Pip> mPipOptional; - private final Optional<OneHanded> mOneHandedOptional; - private final Optional<HideDisplayCutout> mHideDisplayCutout; - private final ShellTaskOrganizer mShellTaskOrganizer; - private final Optional<AppPairs> mAppPairsOptional; - - public ShellDump(ShellTaskOrganizer shellTaskOrganizer, - Optional<SplitScreen> splitScreenOptional, - Optional<Pip> pipOptional, - Optional<OneHanded> oneHandedOptional, - Optional<HideDisplayCutout> hideDisplayCutout, - Optional<AppPairs> appPairsOptional) { - mShellTaskOrganizer = shellTaskOrganizer; - mSplitScreenOptional = splitScreenOptional; - mPipOptional = pipOptional; - mOneHandedOptional = oneHandedOptional; - mHideDisplayCutout = hideDisplayCutout; - mAppPairsOptional = appPairsOptional; - } - - public void dump(PrintWriter pw) { - mShellTaskOrganizer.dump(pw, ""); - pw.println(); - pw.println(); - mPipOptional.ifPresent(pip -> pip.dump(pw)); - mSplitScreenOptional.ifPresent(splitScreen -> splitScreen.dump(pw)); - mOneHandedOptional.ifPresent(oneHanded -> oneHanded.dump(pw)); - mHideDisplayCutout.ifPresent(hideDisplayCutout -> hideDisplayCutout.dump(pw)); - pw.println(); - pw.println(); - mAppPairsOptional.ifPresent(appPairs -> appPairs.dump(pw, "")); - } -} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInit.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInit.java index d654f8adc153..118f189866eb 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInit.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInit.java @@ -16,9 +16,13 @@ package com.android.wm.shell; +import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_FULLSCREEN; +import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_LETTERBOX; + import com.android.wm.shell.apppairs.AppPairs; import com.android.wm.shell.common.DisplayImeController; import com.android.wm.shell.draganddrop.DragAndDropController; +import com.android.wm.shell.letterbox.LetterboxTaskListener; import com.android.wm.shell.splitscreen.SplitScreen; import java.util.Optional; @@ -33,24 +37,36 @@ public class ShellInit { private final ShellTaskOrganizer mShellTaskOrganizer; private final Optional<SplitScreen> mSplitScreenOptional; private final Optional<AppPairs> mAppPairsOptional; + private final LetterboxTaskListener mLetterboxTaskListener; + private final FullscreenTaskListener mFullscreenTaskListener; public ShellInit(DisplayImeController displayImeController, DragAndDropController dragAndDropController, ShellTaskOrganizer shellTaskOrganizer, Optional<SplitScreen> splitScreenOptional, - Optional<AppPairs> appPairsOptional) { + Optional<AppPairs> appPairsOptional, + LetterboxTaskListener letterboxTaskListener, + FullscreenTaskListener fullscreenTaskListener) { mDisplayImeController = displayImeController; mDragAndDropController = dragAndDropController; mShellTaskOrganizer = shellTaskOrganizer; mSplitScreenOptional = splitScreenOptional; mAppPairsOptional = appPairsOptional; + mLetterboxTaskListener = letterboxTaskListener; + mFullscreenTaskListener = fullscreenTaskListener; } public void init() { // Start listening for display changes mDisplayImeController.startMonitorDisplays(); + + mShellTaskOrganizer.addListenerForType( + mLetterboxTaskListener, TASK_LISTENER_TYPE_LETTERBOX); + mShellTaskOrganizer.addListenerForType( + mFullscreenTaskListener, TASK_LISTENER_TYPE_FULLSCREEN); // Register the shell organizer mShellTaskOrganizer.registerOrganizer(); + mAppPairsOptional.ifPresent(AppPairs::onOrganizerRegistered); // Bind the splitscreen impl to the drag drop controller mDragAndDropController.setSplitScreenController(mSplitScreenOptional); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java index 006fd9d35a28..beb9690b5cbc 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java @@ -118,8 +118,6 @@ public class ShellTaskOrganizer extends TaskOrganizer { SyncTransactionQueue syncQueue, TransactionPool transactionPool, ShellExecutor mainExecutor, ShellExecutor animExecutor, Context context) { super(taskOrganizerController, mainExecutor); - addListenerForType(new FullscreenTaskListener(syncQueue), TASK_LISTENER_TYPE_FULLSCREEN); - addListenerForType(new LetterboxTaskListener(syncQueue), TASK_LISTENER_TYPE_LETTERBOX); mTransitions = new Transitions(this, transactionPool, mainExecutor, animExecutor); if (Transitions.ENABLE_SHELL_TRANSITIONS) registerTransitionPlayer(mTransitions); // TODO(b/131727939) temporarily live here, the starting surface drawer should be controlled diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/letterbox/LetterboxConfigController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/letterbox/LetterboxConfigController.java new file mode 100644 index 000000000000..0a549c6aa7d9 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/letterbox/LetterboxConfigController.java @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.letterbox; + +import android.content.Context; +import android.view.Gravity; + +import com.android.wm.shell.R; + +/** + * Controls access to and overrides of resource config values used by {@link + * LetterboxTaskOrganizer}. + */ +public final class LetterboxConfigController { + + private final Context mContext; + + /** {@link Gravity} of letterboxed apps in portrait screen orientation. */ + private int mLetterboxPortraitGravity; + + /** {@link Gravity} of letterboxed apps in landscape screen orientation. */ + private int mLetterboxLandscapeGravity; + + public LetterboxConfigController(Context context) { + mContext = context; + mLetterboxPortraitGravity = + mContext.getResources().getInteger(R.integer.config_letterboxPortraitGravity); + mLetterboxLandscapeGravity = + mContext.getResources().getInteger(R.integer.config_letterboxLandscapeGravity); + } + + /** + * Overrides {@link Gravity} of letterboxed apps in portrait screen orientation. + * + * @throws IllegalArgumentException if gravity isn't equal to {@link Gravity#TOP}, {@link + * Gravity#CENTER} or {@link Gravity#BOTTOM}. + */ + public void setPortraitGravity(int gravity) { + if (gravity != Gravity.TOP && gravity != Gravity.CENTER && gravity != Gravity.BOTTOM) { + throw new IllegalArgumentException( + "Expected Gravity#TOP, Gravity#CENTER or Gravity#BOTTOM but got" + + gravity); + } + mLetterboxPortraitGravity = gravity; + } + + /** + * Resets {@link Gravity} of letterboxed apps in portrait screen orientation to {@link + * R.integer.config_letterboxPortraitGravity}. + */ + public void resetPortraitGravity() { + mLetterboxPortraitGravity = + mContext.getResources().getInteger(R.integer.config_letterboxPortraitGravity); + } + + /** + * Gets {@link Gravity} of letterboxed apps in portrait screen orientation. + */ + public int getPortraitGravity() { + return mLetterboxPortraitGravity; + } + + /** + * Overrides {@link Gravity} of letterboxed apps in landscape screen orientation. + * + * @throws IllegalArgumentException if gravity isn't equal to {@link Gravity#RIGHT}, {@link + * Gravity#CENTER} or {@link Gravity#LEFT}. + */ + public void setLandscapeGravity(int gravity) { + if (gravity != Gravity.LEFT && gravity != Gravity.CENTER && gravity != Gravity.RIGHT) { + throw new IllegalArgumentException( + "Expected Gravity#LEFT, Gravity#CENTER or Gravity#RIGHT but got" + + gravity); + } + mLetterboxLandscapeGravity = gravity; + } + + /** + * Resets {@link Gravity} of letterboxed apps in landscape screen orientation to {@link + * R.integer.config_letterboxLandscapeGravity}. + */ + public void resetLandscapeGravity() { + mLetterboxLandscapeGravity = + mContext.getResources().getInteger(R.integer.config_letterboxLandscapeGravity); + } + + /** + * Gets {@link Gravity} of letterboxed apps in landscape screen orientation. + */ + public int getLandscapeGravity() { + return mLetterboxLandscapeGravity; + } + +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/letterbox/LetterboxTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/letterbox/LetterboxTaskListener.java new file mode 100644 index 000000000000..061d3f86b669 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/letterbox/LetterboxTaskListener.java @@ -0,0 +1,226 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.letterbox; + +import android.app.ActivityManager; +import android.graphics.Insets; +import android.graphics.Point; +import android.graphics.Rect; +import android.util.Slog; +import android.util.SparseArray; +import android.view.Gravity; +import android.view.SurfaceControl; +import android.view.WindowInsets; +import android.view.WindowManager; + +import com.android.internal.protolog.common.ProtoLog; +import com.android.wm.shell.ShellTaskOrganizer; +import com.android.wm.shell.Transitions; +import com.android.wm.shell.common.SyncTransactionQueue; +import com.android.wm.shell.protolog.ShellProtoLogGroup; + +/** + * Organizes a task in {@link android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN} when + * it's presented in the letterbox mode either because orientations of a top activity and a device + * don't match or because a top activity is in a size compat mode. + */ +public class LetterboxTaskListener implements ShellTaskOrganizer.TaskListener { + private static final String TAG = "LetterboxTaskListener"; + + private final SyncTransactionQueue mSyncQueue; + private final LetterboxConfigController mLetterboxConfigController; + private final WindowManager mWindowManager; + private final SparseArray<SurfaceControl> mLeashByTaskId = new SparseArray<>(); + + public LetterboxTaskListener( + SyncTransactionQueue syncQueue, + LetterboxConfigController letterboxConfigController, + WindowManager windowManager) { + mSyncQueue = syncQueue; + mLetterboxConfigController = letterboxConfigController; + mWindowManager = windowManager; + } + + @Override + public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) { + if (mLeashByTaskId.get(taskInfo.taskId) != null) { + throw new IllegalStateException("Task appeared more than once: #" + taskInfo.taskId); + } + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Letterbox Task Appeared: #%d", + taskInfo.taskId); + mLeashByTaskId.put(taskInfo.taskId, leash); + Point positionInParent = new Point(); + Rect crop = new Rect(); + resolveTaskPositionAndCrop(taskInfo, positionInParent, crop); + mSyncQueue.runInSync(t -> { + setPositionAndWindowCrop(t, leash, positionInParent, crop); + if (!Transitions.ENABLE_SHELL_TRANSITIONS) { + t.setAlpha(leash, 1f); + t.setMatrix(leash, 1, 0, 0, 1); + t.show(leash); + } + }); + } + + @Override + public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) { + if (mLeashByTaskId.get(taskInfo.taskId) == null) { + Slog.e(TAG, "Task already vanished: #" + taskInfo.taskId); + return; + } + mLeashByTaskId.remove(taskInfo.taskId); + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Letterbox Task Vanished: #%d", + taskInfo.taskId); + } + + @Override + public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Letterbox Task Changed: #%d", + taskInfo.taskId); + final SurfaceControl leash = mLeashByTaskId.get(taskInfo.taskId); + Point positionInParent = new Point(); + Rect crop = new Rect(); + resolveTaskPositionAndCrop(taskInfo, positionInParent, crop); + mSyncQueue.runInSync(t -> setPositionAndWindowCrop(t, leash, positionInParent, crop)); + } + + private static void setPositionAndWindowCrop( + SurfaceControl.Transaction transaction, + SurfaceControl leash, + final Point positionInParent, + final Rect crop) { + transaction.setPosition(leash, positionInParent.x, positionInParent.y); + transaction.setWindowCrop(leash, crop); + } + + private void resolveTaskPositionAndCrop( + ActivityManager.RunningTaskInfo taskInfo, + Point positionInParent, + Rect crop) { + // In screen coordinates + Rect parentBounds = new Rect(taskInfo.parentBounds); + // Intersect parent and max bounds. This is required for situations when parent bounds + // go beyond display bounds, for example, in One-handed mode. + final Rect maxBounds = taskInfo.getConfiguration().windowConfiguration.getMaxBounds(); + if (!parentBounds.intersect(maxBounds)) { + Slog.w(TAG, "Task parent and max bounds don't intersect: #" + taskInfo.taskId); + } + + // In screen coordinates + final Rect taskBounds = taskInfo.getConfiguration().windowConfiguration.getBounds(); + final Rect activityBounds = taskInfo.letterboxActivityBounds; + + Insets insets = getInsets(); + + Rect taskBoundsWithInsets = new Rect(taskBounds); + applyInsets(taskBoundsWithInsets, insets, taskInfo.parentBounds); + + Rect activityBoundsWithInsets = new Rect(activityBounds); + applyInsets(activityBoundsWithInsets, insets, taskInfo.parentBounds); + + Rect parentBoundsWithInsets = new Rect(parentBounds); + applyInsets(parentBoundsWithInsets, insets, parentBounds); + + // Crop need to be in the task coordinates. + crop.set(activityBoundsWithInsets); + crop.offset(-taskBounds.left, -taskBounds.top); + + // Account for insets since coordinates calculations below are done with them. + positionInParent.x = parentBoundsWithInsets.left - parentBounds.left + - (taskBoundsWithInsets.left - taskBounds.left); + positionInParent.y = parentBoundsWithInsets.top - parentBounds.top + - (taskBoundsWithInsets.top - taskBounds.top); + + // Calculating a position of task bounds (without insets) in parent coordinates (without + // insets) to align activity bounds (without insets) as requested in config. Activity + // accounts for insets that overlap with its bounds (this overlap can be partial) so + // ignoring overlap with insets when computing the position. Also, cropping unwanted insets + // while keeping the top one if the activity is aligned at the top of the window to show + // status bar decor view. + if (parentBounds.height() >= parentBounds.width()) { + final int gravity = mLetterboxConfigController.getPortraitGravity(); + // Center activity horizontally. + positionInParent.x += + (parentBoundsWithInsets.width() - activityBoundsWithInsets.width()) / 2 + + taskBoundsWithInsets.left - activityBoundsWithInsets.left; + switch (gravity) { + case Gravity.TOP: + positionInParent.y += taskBoundsWithInsets.top - activityBoundsWithInsets.top; + // Showing status bar decor view. + crop.top -= activityBoundsWithInsets.top - activityBounds.top; + break; + case Gravity.CENTER: + positionInParent.y += + taskBoundsWithInsets.top - activityBoundsWithInsets.top + + (parentBoundsWithInsets.height() + - activityBoundsWithInsets.height()) / 2; + break; + case Gravity.BOTTOM: + positionInParent.y += + parentBoundsWithInsets.height() - activityBoundsWithInsets.bottom + + taskBoundsWithInsets.top; + break; + default: + throw new AssertionError( + "Unexpected portrait gravity " + gravity + + " for task: #" + taskInfo.taskId); + } + } else { + final int gravity = mLetterboxConfigController.getLandscapeGravity(); + // Align activity to the top. + positionInParent.y += taskBoundsWithInsets.top - activityBoundsWithInsets.top; + // Showing status bar decor view. + crop.top -= activityBoundsWithInsets.top - activityBounds.top; + switch (gravity) { + case Gravity.LEFT: + positionInParent.x += taskBoundsWithInsets.left - activityBoundsWithInsets.left; + break; + case Gravity.CENTER: + positionInParent.x += + (parentBoundsWithInsets.width() - activityBoundsWithInsets.width()) / 2 + + taskBoundsWithInsets.left - activityBoundsWithInsets.left; + break; + case Gravity.RIGHT: + positionInParent.x += + parentBoundsWithInsets.width() + - activityBoundsWithInsets.right + taskBoundsWithInsets.left; + break; + default: + throw new AssertionError( + "Unexpected landscape gravity " + gravity + + " for task: #" + taskInfo.taskId); + } + } + } + + private Insets getInsets() { + return mWindowManager + .getMaximumWindowMetrics() + .getWindowInsets() + .getInsets( + WindowInsets.Type.navigationBars() + | WindowInsets.Type.statusBars() + | WindowInsets.Type.displayCutout()); + } + + private void applyInsets(Rect innerBounds, Insets insets, Rect outerBounds) { + Rect outerBoundsWithInsets = new Rect(outerBounds); + outerBoundsWithInsets.inset(insets); + innerBounds.intersect(outerBoundsWithInsets); + } + +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/LetterboxTaskListenerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/LetterboxTaskListenerTest.java deleted file mode 100644 index 45d4d5d347dd..000000000000 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/LetterboxTaskListenerTest.java +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell; - -import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; - -import static org.mockito.ArgumentMatchers.eq; - -import android.app.ActivityManager.RunningTaskInfo; -import android.graphics.Point; -import android.graphics.Rect; -import android.os.Handler; -import android.os.Looper; -import android.view.SurfaceControl; - -import androidx.test.ext.junit.runners.AndroidJUnit4; -import androidx.test.filters.SmallTest; - -import com.android.wm.shell.common.SyncTransactionQueue; -import com.android.wm.shell.common.TransactionPool; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -/** - * Tests for {@link LetterboxTaskListener}. - */ -@SmallTest -@RunWith(AndroidJUnit4.class) -public class LetterboxTaskListenerTest { - - private static final Rect ACTIVITY_BOUNDS = new Rect(300, 200, 700, 400); - private static final Rect TASK_BOUNDS = new Rect(200, 100, 800, 500); - private static final Rect TASK_BOUNDS_2 = new Rect(300, 200, 800, 500); - private static final Point TASK_POSITION_IN_PARENT = new Point(100, 50); - private static final Point TASK_POSITION_IN_PARENT_2 = new Point(200, 100); - - private static final Rect EXPECTED_WINDOW_CROP = new Rect(100, 100, 500, 300); - private static final Rect EXPECTED_WINDOW_CROP_2 = new Rect(0, 0, 400, 200); - - private static final RunningTaskInfo TASK_INFO = createTaskInfo( - /* taskId */ 1, ACTIVITY_BOUNDS, TASK_BOUNDS, TASK_POSITION_IN_PARENT); - - private static final RunningTaskInfo TASK_INFO_2 = createTaskInfo( - /* taskId */ 1, ACTIVITY_BOUNDS, TASK_BOUNDS_2, TASK_POSITION_IN_PARENT_2); - - @Mock private SurfaceControl mLeash; - @Mock private SurfaceControl.Transaction mTransaction; - private LetterboxTaskListener mLetterboxTaskListener; - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - mLetterboxTaskListener = new LetterboxTaskListener( - new SyncTransactionQueue( - new TransactionPool() { - @Override - public SurfaceControl.Transaction acquire() { - return mTransaction; - } - - @Override - public void release(SurfaceControl.Transaction t) { - } - }, - new Handler(Looper.getMainLooper()))); - } - - @Test - public void testOnTaskAppearedAndonTaskInfoChanged_setCorrectPositionAndCrop() { - mLetterboxTaskListener.onTaskAppeared(TASK_INFO, mLeash); - - verify(mTransaction).setPosition( - eq(mLeash), - eq((float) TASK_POSITION_IN_PARENT.x), - eq((float) TASK_POSITION_IN_PARENT.y)); - // Should return activty coordinates offset by task coordinates - verify(mTransaction).setWindowCrop(eq(mLeash), eq(EXPECTED_WINDOW_CROP)); - - mLetterboxTaskListener.onTaskInfoChanged(TASK_INFO_2); - - verify(mTransaction).setPosition( - eq(mLeash), - eq((float) TASK_POSITION_IN_PARENT_2.x), - eq((float) TASK_POSITION_IN_PARENT_2.y)); - // Should return activty coordinates offset by task coordinates - verify(mTransaction).setWindowCrop(eq(mLeash), eq(EXPECTED_WINDOW_CROP_2)); - } - - @Test(expected = RuntimeException.class) - public void testOnTaskAppeared_calledSecondTimeWithSameTaskId_throwsException() { - mLetterboxTaskListener.onTaskAppeared(TASK_INFO, mLeash); - mLetterboxTaskListener.onTaskAppeared(TASK_INFO, mLeash); - } - - private static RunningTaskInfo createTaskInfo( - int taskId, - final Rect activityBounds, - final Rect taskBounds, - final Point taskPositionInParent) { - RunningTaskInfo taskInfo = new RunningTaskInfo(); - taskInfo.taskId = taskId; - taskInfo.configuration.windowConfiguration.setBounds(taskBounds); - taskInfo.letterboxActivityBounds = Rect.copyOrNull(activityBounds); - taskInfo.positionInParent = new Point(taskPositionInParent); - return taskInfo; - } -} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/letterbox/LetterboxConfigControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/letterbox/LetterboxConfigControllerTest.java new file mode 100644 index 000000000000..29233366d4f3 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/letterbox/LetterboxConfigControllerTest.java @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.letterbox; + +import static org.junit.Assert.assertEquals; + +import android.view.Gravity; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; + +import com.android.wm.shell.R; +import com.android.wm.shell.ShellTestCase; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Tests for {@link LetterboxConfigController}. + */ +@SmallTest +@RunWith(AndroidJUnit4.class) +public final class LetterboxConfigControllerTest extends ShellTestCase { + + private LetterboxConfigController mLetterboxConfigController; + + @Before + public void setUp() { + mLetterboxConfigController = new LetterboxConfigController(getContext()); + } + + @Test + public void testGetPortraitGravity_noOverrides_returnConfigValue() { + assertEquals( + mLetterboxConfigController.getPortraitGravity(), + getContext().getResources().getInteger(R.integer.config_letterboxPortraitGravity)); + } + + @Test + public void testGetLandscapeGravity_noOverrides_returnConfigValue() { + assertEquals( + mLetterboxConfigController.getLandscapeGravity(), + getContext().getResources().getInteger(R.integer.config_letterboxLandscapeGravity)); + } + + @Test + public void testSetPortraitGravity_validValue_savesValue() { + mLetterboxConfigController.setPortraitGravity(Gravity.BOTTOM); + assertEquals(mLetterboxConfigController.getPortraitGravity(), Gravity.BOTTOM); + + mLetterboxConfigController.setPortraitGravity(Gravity.CENTER); + assertEquals(mLetterboxConfigController.getPortraitGravity(), Gravity.CENTER); + + mLetterboxConfigController.setPortraitGravity(Gravity.TOP); + assertEquals(mLetterboxConfigController.getPortraitGravity(), Gravity.TOP); + } + + @Test + public void testSetLandscapeGravity_validValue_savesValue() { + mLetterboxConfigController.setLandscapeGravity(Gravity.LEFT); + assertEquals(mLetterboxConfigController.getLandscapeGravity(), Gravity.LEFT); + + mLetterboxConfigController.setLandscapeGravity(Gravity.CENTER); + assertEquals(mLetterboxConfigController.getLandscapeGravity(), Gravity.CENTER); + + mLetterboxConfigController.setLandscapeGravity(Gravity.RIGHT); + assertEquals(mLetterboxConfigController.getLandscapeGravity(), Gravity.RIGHT); + } + + @Test(expected = IllegalArgumentException.class) + public void testSetPortraitGravity_invalidValue_throwsException() { + mLetterboxConfigController.setPortraitGravity(Gravity.RIGHT); + } + + @Test(expected = IllegalArgumentException.class) + public void testSetLandscapeGravity_invalidValue_throwsException() { + mLetterboxConfigController.setLandscapeGravity(Gravity.TOP); + } + + @Test + public void testResetPortraitGravity() { + int defaultGravity = + getContext().getResources().getInteger(R.integer.config_letterboxPortraitGravity); + + mLetterboxConfigController.setPortraitGravity(Gravity.BOTTOM); + mLetterboxConfigController.resetPortraitGravity(); + assertEquals(mLetterboxConfigController.getPortraitGravity(), defaultGravity); + + mLetterboxConfigController.setPortraitGravity(Gravity.CENTER); + mLetterboxConfigController.resetPortraitGravity(); + assertEquals(mLetterboxConfigController.getPortraitGravity(), defaultGravity); + + mLetterboxConfigController.setPortraitGravity(Gravity.TOP); + mLetterboxConfigController.resetPortraitGravity(); + assertEquals(mLetterboxConfigController.getPortraitGravity(), defaultGravity); + } + + @Test + public void testResetLandscapeGravity() { + int defaultGravity = + getContext().getResources().getInteger(R.integer.config_letterboxLandscapeGravity); + + mLetterboxConfigController.setLandscapeGravity(Gravity.RIGHT); + mLetterboxConfigController.resetLandscapeGravity(); + assertEquals(mLetterboxConfigController.getLandscapeGravity(), defaultGravity); + + mLetterboxConfigController.setLandscapeGravity(Gravity.CENTER); + mLetterboxConfigController.resetLandscapeGravity(); + assertEquals(mLetterboxConfigController.getLandscapeGravity(), defaultGravity); + + mLetterboxConfigController.setLandscapeGravity(Gravity.LEFT); + mLetterboxConfigController.resetLandscapeGravity(); + assertEquals(mLetterboxConfigController.getLandscapeGravity(), defaultGravity); + } + +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/letterbox/LetterboxTaskListenerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/letterbox/LetterboxTaskListenerTest.java new file mode 100644 index 000000000000..0f719afd08b3 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/letterbox/LetterboxTaskListenerTest.java @@ -0,0 +1,320 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.letterbox; + +import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; + +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; + +import android.app.ActivityManager.RunningTaskInfo; +import android.graphics.Insets; +import android.graphics.Rect; +import android.os.Handler; +import android.os.Looper; +import android.view.Gravity; +import android.view.SurfaceControl; +import android.view.WindowInsets; +import android.view.WindowManager; +import android.view.WindowMetrics; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; + +import com.android.wm.shell.ShellTestCase; +import com.android.wm.shell.common.SyncTransactionQueue; +import com.android.wm.shell.common.TransactionPool; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Tests for {@link LetterboxTaskListener}. + */ +@SmallTest +@RunWith(AndroidJUnit4.class) +public final class LetterboxTaskListenerTest extends ShellTestCase { + + @Mock private SurfaceControl mLeash; + @Mock private SurfaceControl.Transaction mTransaction; + @Mock private WindowManager mWindowManager; + @Mock private WindowMetrics mWindowMetrics; + @Mock private WindowInsets mWindowInsets; + private LetterboxTaskListener mLetterboxTaskListener; + private LetterboxConfigController mLetterboxConfigController; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + mLetterboxConfigController = new LetterboxConfigController(getContext()); + mLetterboxTaskListener = new LetterboxTaskListener( + new SyncTransactionQueue( + new TransactionPool() { + @Override + public SurfaceControl.Transaction acquire() { + return mTransaction; + } + + @Override + public void release(SurfaceControl.Transaction t) { + } + }, + new Handler(Looper.getMainLooper())), + mLetterboxConfigController, + mWindowManager); + + when(mWindowManager.getMaximumWindowMetrics()).thenReturn(mWindowMetrics); + when(mWindowMetrics.getWindowInsets()).thenReturn(mWindowInsets); + } + + @Test + public void testOnTaskInfoChanged_updatesPositionAndCrop() { + setWindowBoundsAndInsets( + /* windowBounds= */ new Rect(0, 0, 200, 100), // equal to parent bounds + Insets.NONE); + + mLetterboxConfigController.setLandscapeGravity(Gravity.CENTER); + mLetterboxTaskListener.onTaskAppeared( + createTaskInfo( + /* taskId */ 1, + /* parentBounds */ new Rect(0, 0, 200, 100), + /* activityBounds */ new Rect(75, 0, 125, 75), + /* taskBounds */ new Rect(50, 0, 125, 100)), + mLeash); + + // Task doesn't need to repositioned + verifySetPosition(50, 0); + // Should return activity coordinates offset by task coordinates + verifySetWindowCrop(new Rect(25, 0, 75, 75)); + + mLetterboxTaskListener.onTaskInfoChanged( + createTaskInfo( + /* taskId */ 1, + /* parentBounds */ new Rect(0, 0, 200, 100), + // Activity is offset by 25 to the left + /* activityBounds */ new Rect(50, 0, 100, 75), + /* taskBounds */ new Rect(50, 0, 125, 100))); + + // Task needs to be repositioned by 25 to the left + verifySetPosition(75, 0); + // Should return activity coordinates offset by task coordinates + verifySetWindowCrop(new Rect(0, 0, 50, 75)); + } + + @Test + public void testOnTaskInfoAppeared_landscapeWithLeftGravity() { + mLetterboxConfigController.setLandscapeGravity(Gravity.LEFT); + setWindowBoundsAndInsets( + /* windowBounds= */ new Rect(0, 0, 200, 100), // equal to parent bounds + Insets.of(/* left= */ 10, /* top= */ 10, /* right= */ 10, /* bottom= */ 10)); + + mLetterboxTaskListener.onTaskAppeared( + createTaskInfo( + /* taskId */ 1, + /* parentBounds */ new Rect(0, 0, 200, 100), + /* activityBounds */ new Rect(150, 0, 200, 75), + /* taskBounds */ new Rect(125, 0, 200, 100)), + mLeash); + + verifySetPosition(-15, 0); + // Should return activity coordinates offset by task coordinates minus unwanted right inset + verifySetWindowCrop(new Rect(25, 0, 65, 75)); + } + + @Test + public void testOnTaskInfoAppeared_landscapeWithCenterGravity() { + mLetterboxConfigController.setLandscapeGravity(Gravity.CENTER); + setWindowBoundsAndInsets( + /* windowBounds= */ new Rect(0, 0, 200, 100), // equal to parent bounds + Insets.of(/* left= */ 10, /* top= */ 10, /* right= */ 10, /* bottom= */ 10)); + + mLetterboxTaskListener.onTaskAppeared( + createTaskInfo( + /* taskId */ 1, + /* parentBounds */ new Rect(0, 0, 200, 100), + /* activityBounds */ new Rect(150, 0, 200, 75), + /* taskBounds */ new Rect(125, 0, 200, 100)), + mLeash); + + verifySetPosition(55, 0); + // Should return activity coordinates offset by task coordinates minus unwanted right inset + verifySetWindowCrop(new Rect(25, 0, 65, 75)); + } + + @Test + public void testOnTaskInfoAppeared_landscapeWithRightGravity() { + mLetterboxConfigController.setLandscapeGravity(Gravity.RIGHT); + setWindowBoundsAndInsets( + /* windowBounds= */ new Rect(0, 0, 200, 100), // equal to parent bounds + Insets.of(/* left= */ 10, /* top= */ 10, /* right= */ 10, /* bottom= */ 10)); + + mLetterboxTaskListener.onTaskAppeared( + createTaskInfo( + /* taskId */ 1, + /* parentBounds */ new Rect(0, 0, 200, 100), + /* activityBounds */ new Rect(50, 0, 100, 75), + /* taskBounds */ new Rect(25, 0, 100, 100)), + mLeash); + + verifySetPosition(115, 0); + // Should return activity coordinates offset by task coordinates + verifySetWindowCrop(new Rect(25, 0, 75, 75)); + } + + @Test + public void testOnTaskInfoAppeared_portraitWithTopGravity() { + mLetterboxConfigController.setPortraitGravity(Gravity.TOP); + setWindowBoundsAndInsets( + /* windowBounds= */ new Rect(0, 0, 100, 150), // equal to parent bounds + Insets.of(/* left= */ 10, /* top= */ 10, /* right= */ 10, /* bottom= */ 20)); + + mLetterboxTaskListener.onTaskAppeared( + createTaskInfo( + /* taskId */ 1, + /* parentBounds */ new Rect(0, 0, 100, 150), + /* activityBounds */ new Rect(0, 75, 50, 125), + /* taskBounds */ new Rect(0, 50, 100, 125)), + mLeash); + + verifySetPosition(20, -15); + // Should return activity coordinates offset by task coordinates minus unwanted left inset + verifySetWindowCrop(new Rect(10, 25, 50, 75)); + } + + @Test + public void testOnTaskInfoAppeared_portraitWithCenterGravity() { + mLetterboxConfigController.setPortraitGravity(Gravity.CENTER); + setWindowBoundsAndInsets( + /* windowBounds= */ new Rect(0, 0, 100, 150), // equal to parent bounds + Insets.of(/* left= */ 10, /* top= */ 10, /* right= */ 10, /* bottom= */ 20)); + + mLetterboxTaskListener.onTaskAppeared( + createTaskInfo( + /* taskId */ 1, + /* parentBounds */ new Rect(0, 0, 100, 150), + /* activityBounds */ new Rect(0, 75, 50, 125), + /* taskBounds */ new Rect(0, 50, 100, 125)), + mLeash); + + verifySetPosition(20, 20); + // Should return activity coordinates offset by task coordinates minus unwanted left inset + verifySetWindowCrop(new Rect(10, 25, 50, 75)); + } + + @Test + public void testOnTaskInfoAppeared_portraitWithBottomGravity() { + mLetterboxConfigController.setPortraitGravity(Gravity.BOTTOM); + setWindowBoundsAndInsets( + /* windowBounds= */ new Rect(0, 0, 100, 150), // equal to parent bounds + Insets.of(/* left= */ 10, /* top= */ 10, /* right= */ 10, /* bottom= */ 20)); + + mLetterboxTaskListener.onTaskAppeared( + createTaskInfo( + /* taskId */ 1, + /* parentBounds */ new Rect(0, 0, 100, 150), + /* activityBounds */ new Rect(0, 75, 50, 125), + /* taskBounds */ new Rect(0, 50, 100, 125)), + mLeash); + + verifySetPosition(20, 55); + // Should return activity coordinates offset by task coordinates minus unwanted left inset + verifySetWindowCrop(new Rect(10, 25, 50, 75)); + } + + @Test + public void testOnTaskInfoAppeared_partlyOverlapsWithAllInsets() { + mLetterboxConfigController.setPortraitGravity(Gravity.TOP); + setWindowBoundsAndInsets( + /* windowBounds= */ new Rect(0, 0, 200, 125), // equal to parent bounds + Insets.of(/* left= */ 25, /* top= */ 25, /* right= */ 35, /* bottom= */ 15)); + + mLetterboxTaskListener.onTaskAppeared( + createTaskInfo( + /* taskId */ 1, + /* parentBounds */ new Rect(0, 0, 200, 125), + /* activityBounds */ new Rect(15, 0, 175, 120), + /* taskBounds */ new Rect(0, 0, 100, 125)), // equal to parent bounds + mLeash); + + // Activity fully covers parent bounds with insets so doesn't need to be moved. + verifySetPosition(0, 0); + // Should return activity coordinates offset by task coordinates minus all insets + // except top one (keep status bar decor visible). + verifySetWindowCrop(new Rect(25, 0, 165, 110)); + } + + @Test + public void testOnTaskInfoAppeared_parentShiftedLikeInOneHandedMode() { + mLetterboxConfigController.setPortraitGravity(Gravity.TOP); + setWindowBoundsAndInsets( + /* windowBounds= */ new Rect(0, 0, 100, 150), + Insets.of(/* left= */ 0, /* top= */ 10, /* right= */ 0, /* bottom= */ 0)); + + mLetterboxTaskListener.onTaskAppeared( + createTaskInfo( + /* taskId */ 1, + /* parentBounds */ new Rect(0, 75, 100, 225), + /* activityBounds */ new Rect(25, 75, 75, 125), + /* taskBounds */ new Rect(0, 75, 100, 125)), + mLeash); + + verifySetPosition(0, 0); + verifySetWindowCrop(new Rect(25, 0, 75, 50)); + } + + @Test(expected = IllegalStateException.class) + public void testOnTaskAppeared_calledSecondTimeWithSameTaskId_throwsException() { + setWindowBoundsAndInsets(new Rect(), Insets.NONE); + RunningTaskInfo taskInfo = + createTaskInfo(/* taskId */ 1, new Rect(), new Rect(), new Rect()); + mLetterboxTaskListener.onTaskAppeared(taskInfo, mLeash); + mLetterboxTaskListener.onTaskAppeared(taskInfo, mLeash); + } + + private void setWindowBoundsAndInsets(Rect windowBounds, Insets insets) { + when(mWindowMetrics.getBounds()).thenReturn(windowBounds); + when(mWindowInsets.getInsets(anyInt())).thenReturn(insets); + } + + private void verifySetPosition(int x, int y) { + verify(mTransaction).setPosition(eq(mLeash), eq((float) x), eq((float) y)); + } + + private void verifySetWindowCrop(final Rect crop) { + // Should return activty coordinates offset by task coordinates + verify(mTransaction).setWindowCrop(eq(mLeash), eq(crop)); + } + + private static RunningTaskInfo createTaskInfo( + int taskId, + final Rect parentBounds, + final Rect activityBounds, + final Rect taskBounds) { + RunningTaskInfo taskInfo = new RunningTaskInfo(); + taskInfo.taskId = taskId; + taskInfo.parentBounds = parentBounds; + taskInfo.configuration.windowConfiguration.setBounds(taskBounds); + taskInfo.letterboxActivityBounds = Rect.copyOrNull(activityBounds); + + return taskInfo; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java index 11180d131e70..8e5e9ea257ba 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java @@ -110,7 +110,7 @@ public class SystemUIFactory { .setOneHanded(mWMComponent.getOneHanded()) .setBubbles(mWMComponent.getBubbles()) .setHideDisplayCutout(mWMComponent.getHideDisplayCutout()) - .setShellDump(mWMComponent.getShellDump()) + .setShellCommandHandler(mWMComponent.getShellCommandHandler()) .setAppPairs(mWMComponent.getAppPairs()); } else { // TODO: Call on prepareSysUIComponentBuilder but not with real components. @@ -119,7 +119,7 @@ public class SystemUIFactory { .setOneHanded(Optional.ofNullable(null)) .setBubbles(Optional.ofNullable(null)) .setHideDisplayCutout(Optional.ofNullable(null)) - .setShellDump(Optional.ofNullable(null)) + .setShellCommandHandler(Optional.ofNullable(null)) .setAppPairs(Optional.ofNullable(null)); } mSysUIComponent = builder.build(); diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java index 68a28ba63d6f..f9f68e22d022 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java @@ -24,7 +24,7 @@ import com.android.systemui.dump.DumpManager; import com.android.systemui.keyguard.KeyguardSliceProvider; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.util.InjectionInflationController; -import com.android.wm.shell.ShellDump; +import com.android.wm.shell.ShellCommandHandler; import com.android.wm.shell.apppairs.AppPairs; import com.android.wm.shell.bubbles.Bubbles; import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout; @@ -74,7 +74,7 @@ public interface SysUIComponent { Builder setHideDisplayCutout(Optional<HideDisplayCutout> h); @BindsInstance - Builder setShellDump(Optional<ShellDump> shellDump); + Builder setShellCommandHandler(Optional<ShellCommandHandler> shellDump); SysUIComponent build(); } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java index e634529dcb71..ec8c7da3ebf6 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java @@ -17,7 +17,7 @@ package com.android.systemui.dagger; import com.android.systemui.wmshell.WMShellModule; -import com.android.wm.shell.ShellDump; +import com.android.wm.shell.ShellCommandHandler; import com.android.wm.shell.ShellInit; import com.android.wm.shell.apppairs.AppPairs; import com.android.wm.shell.bubbles.Bubbles; @@ -58,7 +58,7 @@ public interface WMComponent { // Gets the Shell dump instance @WMSingleton - Optional<ShellDump> getShellDump(); + Optional<ShellCommandHandler> getShellCommandHandler(); // TODO(b/162923491): We currently pass the instances through to SysUI, but that may change // depending on the threading mechanism we go with diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java index c36acf5b6def..34be362f198c 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java @@ -52,7 +52,7 @@ import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.UserInfoController; import com.android.systemui.tracing.ProtoTracer; import com.android.systemui.tracing.nano.SystemUiTraceProto; -import com.android.wm.shell.ShellDump; +import com.android.wm.shell.ShellCommandHandler; import com.android.wm.shell.apppairs.AppPairs; import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout; import com.android.wm.shell.nano.WmShellTraceProto; @@ -98,7 +98,7 @@ public final class WMShell extends SystemUI private final Optional<OneHanded> mOneHandedOptional; private final Optional<HideDisplayCutout> mHideDisplayCutoutOptional; private final ProtoTracer mProtoTracer; - private final Optional<ShellDump> mShellDump; + private final Optional<ShellCommandHandler> mShellCommandHandler; private final Optional<AppPairs> mAppPairsOptional; private boolean mIsSysUiStateValid; @@ -118,7 +118,7 @@ public final class WMShell extends SystemUI Optional<OneHanded> oneHandedOptional, Optional<HideDisplayCutout> hideDisplayCutoutOptional, ProtoTracer protoTracer, - Optional<ShellDump> shellDump, + Optional<ShellCommandHandler> shellCommandHandler, Optional<AppPairs> appPairsOptional) { super(context); mCommandQueue = commandQueue; @@ -133,7 +133,7 @@ public final class WMShell extends SystemUI mHideDisplayCutoutOptional = hideDisplayCutoutOptional; mProtoTracer = protoTracer; mProtoTracer.add(this); - mShellDump = shellDump; + mShellCommandHandler = shellCommandHandler; mAppPairsOptional = appPairsOptional; } @@ -304,11 +304,17 @@ public final class WMShell extends SystemUI @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { // Handle commands if provided + if (mShellCommandHandler.isPresent() + && mShellCommandHandler.get().handleCommand(args, pw)) { + return; + } + // Handle logging commands if provided if (handleLoggingCommand(args, pw)) { return; } // Dump WMShell stuff here if no commands were handled - mShellDump.ifPresent((shellDump) -> shellDump.dump(pw)); + mShellCommandHandler.ifPresent( + shellCommandHandler -> shellCommandHandler.dump(pw)); } @Override @@ -339,21 +345,6 @@ public final class WMShell extends SystemUI } return true; } - - case "pair": { - String[] groups = Arrays.copyOfRange(args, i + 1, args.length); - final int taskId1 = new Integer(groups[0]); - final int taskId2 = new Integer(groups[1]); - mAppPairsOptional.ifPresent(appPairs -> appPairs.pair(taskId1, taskId2)); - return true; - } - - case "unpair": { - String[] groups = Arrays.copyOfRange(args, i + 1, args.length); - final int taskId = new Integer(groups[0]); - mAppPairsOptional.ifPresent(appPairs -> appPairs.unpair(taskId)); - return true; - } } } return false; diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java index 74aa1a7d0749..be5ed0ccbef0 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java @@ -28,7 +28,8 @@ import com.android.internal.logging.UiEventLogger; import com.android.internal.statusbar.IStatusBarService; import com.android.systemui.dagger.WMSingleton; import com.android.systemui.dagger.qualifiers.Main; -import com.android.wm.shell.ShellDump; +import com.android.wm.shell.FullscreenTaskListener; +import com.android.wm.shell.ShellCommandHandler; import com.android.wm.shell.ShellInit; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.WindowManagerShellWrapper; @@ -48,6 +49,8 @@ import com.android.wm.shell.common.TransactionPool; import com.android.wm.shell.draganddrop.DragAndDropController; import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout; import com.android.wm.shell.hidedisplaycutout.HideDisplayCutoutController; +import com.android.wm.shell.letterbox.LetterboxConfigController; +import com.android.wm.shell.letterbox.LetterboxTaskListener; import com.android.wm.shell.onehanded.OneHanded; import com.android.wm.shell.onehanded.OneHandedController; import com.android.wm.shell.pip.Pip; @@ -77,12 +80,16 @@ public abstract class WMShellBaseModule { DragAndDropController dragAndDropController, ShellTaskOrganizer shellTaskOrganizer, Optional<SplitScreen> splitScreenOptional, - Optional<AppPairs> appPairsOptional) { + Optional<AppPairs> appPairsOptional, + LetterboxTaskListener letterboxTaskListener, + FullscreenTaskListener fullscreenTaskListener) { return new ShellInit(displayImeController, dragAndDropController, shellTaskOrganizer, splitScreenOptional, - appPairsOptional); + appPairsOptional, + letterboxTaskListener, + fullscreenTaskListener); } /** @@ -91,14 +98,17 @@ public abstract class WMShellBaseModule { */ @WMSingleton @Provides - static Optional<ShellDump> provideShellDump(ShellTaskOrganizer shellTaskOrganizer, + static Optional<ShellCommandHandler> provideShellCommandHandler( + ShellTaskOrganizer shellTaskOrganizer, Optional<SplitScreen> splitScreenOptional, Optional<Pip> pipOptional, Optional<OneHanded> oneHandedOptional, Optional<HideDisplayCutout> hideDisplayCutout, - Optional<AppPairs> appPairsOptional) { - return Optional.of(new ShellDump(shellTaskOrganizer, splitScreenOptional, pipOptional, - oneHandedOptional, hideDisplayCutout, appPairsOptional)); + Optional<AppPairs> appPairsOptional, + LetterboxConfigController letterboxConfigController) { + return Optional.of(new ShellCommandHandler(shellTaskOrganizer, splitScreenOptional, + pipOptional, oneHandedOptional, hideDisplayCutout, appPairsOptional, + letterboxConfigController)); } @WMSingleton @@ -178,8 +188,8 @@ public abstract class WMShellBaseModule { @Provides static ShellTaskOrganizer provideShellTaskOrganizer(SyncTransactionQueue syncQueue, ShellExecutor mainExecutor, TransactionPool transactionPool, Context context) { - return new ShellTaskOrganizer(syncQueue, transactionPool, - mainExecutor, AnimationThread.instance().getExecutor(), context); + return new ShellTaskOrganizer(syncQueue, transactionPool, mainExecutor, + AnimationThread.instance().getExecutor(), context); } @WMSingleton @@ -230,4 +240,27 @@ public abstract class WMShellBaseModule { DisplayController displayController) { return Optional.ofNullable(HideDisplayCutoutController.create(context, displayController)); } + + @WMSingleton + @Provides + static FullscreenTaskListener provideFullscreenTaskListener( + SyncTransactionQueue syncQueue) { + return new FullscreenTaskListener(syncQueue); + } + + @WMSingleton + @Provides + static LetterboxTaskListener provideLetterboxTaskListener( + SyncTransactionQueue syncQueue, + LetterboxConfigController letterboxConfigController, + WindowManager windowManager) { + return new LetterboxTaskListener(syncQueue, letterboxConfigController, windowManager); + } + + @WMSingleton + @Provides + static LetterboxConfigController provideLetterboxConfigController(Context context) { + return new LetterboxConfigController(context); + } + } diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java index a658469f9e2b..822a6f2ad810 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java @@ -33,7 +33,7 @@ import com.android.systemui.navigationbar.NavigationModeController; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.tracing.ProtoTracer; -import com.android.wm.shell.ShellDump; +import com.android.wm.shell.ShellCommandHandler; import com.android.wm.shell.apppairs.AppPairs; import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout; import com.android.wm.shell.onehanded.OneHanded; @@ -68,7 +68,7 @@ public class WMShellTest extends SysuiTestCase { @Mock OneHanded mOneHanded; @Mock HideDisplayCutout mHideDisplayCutout; @Mock ProtoTracer mProtoTracer; - @Mock ShellDump mShellDump; + @Mock ShellCommandHandler mShellCommandHandler; @Mock AppPairs mAppPairs; @Before @@ -79,7 +79,7 @@ public class WMShellTest extends SysuiTestCase { mKeyguardUpdateMonitor, mNavigationModeController, mScreenLifecycle, mSysUiState, Optional.of(mPip), Optional.of(mSplitScreen), Optional.of(mOneHanded), Optional.of(mHideDisplayCutout), mProtoTracer, - Optional.of(mShellDump), Optional.of(mAppPairs)); + Optional.of(mShellCommandHandler), Optional.of(mAppPairs)); when(mPip.getPipTouchHandler()).thenReturn(mPipTouchHandler); } @@ -120,4 +120,4 @@ public class WMShellTest extends SysuiTestCase { verify(mConfigurationController).addCallback( any(ConfigurationController.ConfigurationListener.class)); } -} +}
\ No newline at end of file diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 98c38f96e5ce..bccbae64f241 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -6364,6 +6364,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } } + /* + * Called from {@link RootWindowContainer#ensureVisibilityAndConfig} to make sure the + * orientation is updated before the app becomes visible. + */ void reportDescendantOrientationChangeIfNeeded() { // Orientation request is exposed only when we're visible. Therefore visibility change // will change requested orientation. Notify upward the hierarchy ladder to adjust @@ -6379,6 +6383,13 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // The app is just becoming visible, and the parent Task has updated with the // orientation request. Update the size compat mode. updateSizeCompatMode(); + if (task.isOrganized()) { + // WM Shell can override WM Core positioning (e.g. for letterboxing) so ensure + // that WM Shell is called when an activity becomes visible. Without this, WM Core + // will handle positioning instead of WM Shell when an app is reopened. + mAtmService.mTaskOrganizerController.dispatchTaskInfoChanged( + task, /* force= */ true); + } } } diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 90c3f9ec21ef..a679faa92f67 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -4089,6 +4089,7 @@ class Task extends WindowContainer<WindowContainer> { // assigning bounds from ActivityRecord#layoutLetterbox when they are ready. info.letterboxActivityBounds = Rect.copyOrNull(mLetterboxActivityBounds); info.positionInParent = getRelativePosition(); + info.parentBounds = getParentBounds(); info.pictureInPictureParams = getPictureInPictureParams(); info.topActivityInfo = mReuseActivitiesReport.top != null @@ -4127,6 +4128,11 @@ class Task extends WindowContainer<WindowContainer> { } } + private Rect getParentBounds() { + final WindowContainer parent = getParent(); + return parent != null ? new Rect(parent.getBounds()) : new Rect(); + } + /** * Returns a {@link TaskInfo} with information from this task. */ diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java index bb8b4a5f966f..e64c0476db1d 100644 --- a/services/core/java/com/android/server/wm/TaskOrganizerController.java +++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java @@ -542,11 +542,9 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { || mTmpTaskInfo.topActivityType != lastInfo.topActivityType || mTmpTaskInfo.isResizeable != lastInfo.isResizeable || !Objects.equals( - mTmpTaskInfo.letterboxActivityBounds, - lastInfo.letterboxActivityBounds) - || !Objects.equals( mTmpTaskInfo.positionInParent, lastInfo.positionInParent) + || isLetterboxInfoChanged(lastInfo, mTmpTaskInfo) || mTmpTaskInfo.pictureInPictureParams != lastInfo.pictureInPictureParams || mTmpTaskInfo.getWindowingMode() != lastInfo.getWindowingMode() || !TaskDescription.equals(mTmpTaskInfo.taskDescription, lastInfo.taskDescription); @@ -584,6 +582,20 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { } } + private boolean isLetterboxInfoChanged( + final RunningTaskInfo lastInfo, final RunningTaskInfo currentInfo) { + return !Objects.equals( + currentInfo.letterboxActivityBounds, + lastInfo.letterboxActivityBounds) + || !Objects.equals( + currentInfo.getConfiguration().windowConfiguration.getBounds(), + lastInfo.getConfiguration().windowConfiguration.getBounds()) + || !Objects.equals( + currentInfo.getConfiguration().windowConfiguration.getMaxBounds(), + lastInfo.getConfiguration().windowConfiguration.getMaxBounds()) + || !Objects.equals(currentInfo.parentBounds, lastInfo.parentBounds); + } + @Override public WindowContainerToken getImeTarget(int displayId) { enforceTaskPermission("getImeTarget()"); |