summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Mariia Sandrikova <mariiasand@google.com> 2020-11-17 22:45:44 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2020-11-17 22:45:44 +0000
commit2b54b5d7091f65a879de4c873eee9f60d7382b07 (patch)
tree67973a446f53c087a8126750d6e6e00037981f70
parent687f5e163f24ff31f822f986fd7a99b8832b6286 (diff)
parent49fc5569f0316799ca2bf03a37b6eedb43fda24e (diff)
Merge "Make positioning in letterbox mode configurable."
-rw-r--r--core/java/android/app/TaskInfo.java10
-rw-r--r--libs/WindowManager/Shell/res/raw/wm_shell_protolog.json36
-rw-r--r--libs/WindowManager/Shell/res/values/config.xml12
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/FullscreenTaskListener.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/LetterboxTaskListener.java110
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandler.java231
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/ShellDump.java66
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/ShellInit.java18
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/letterbox/LetterboxConfigController.java108
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/letterbox/LetterboxTaskListener.java226
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/LetterboxTaskListenerTest.java125
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/letterbox/LetterboxConfigControllerTest.java131
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/letterbox/LetterboxTaskListenerTest.java320
-rw-r--r--packages/SystemUI/src/com/android/systemui/SystemUIFactory.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java31
-rw-r--r--packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java51
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java8
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java11
-rw-r--r--services/core/java/com/android/server/wm/Task.java6
-rw-r--r--services/core/java/com/android/server/wm/TaskOrganizerController.java18
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()");