diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java index 8367bde614038..fe382a33a8c52 100644 --- a/core/java/android/app/TaskInfo.java +++ b/core/java/android/app/TaskInfo.java @@ -194,6 +194,14 @@ public class TaskInfo { @Nullable public Rect letterboxActivityBounds; + /** + * Activity insets if this task or its top activity is presented in letterbox mode and + * {@code null} otherwise. + * @hide + */ + @Nullable + public Rect letterboxActivityInsets; + /** * Relative position of the task's top left corner in the parent container. * @hide @@ -328,7 +336,8 @@ public class TaskInfo { && Objects.equals( getConfiguration().windowConfiguration.getMaxBounds(), that.getConfiguration().windowConfiguration.getMaxBounds()) - && Objects.equals(parentBounds, that.parentBounds); + && Objects.equals(parentBounds, that.parentBounds) + && Objects.equals(letterboxActivityInsets, that.letterboxActivityInsets); } /** @@ -365,6 +374,7 @@ public class TaskInfo { parentBounds = source.readTypedObject(Rect.CREATOR); isFocused = source.readBoolean(); isVisible = source.readBoolean(); + letterboxActivityInsets = source.readTypedObject(Rect.CREATOR); } /** @@ -402,6 +412,7 @@ public class TaskInfo { dest.writeTypedObject(parentBounds, flags); dest.writeBoolean(isFocused); dest.writeBoolean(isVisible); + dest.writeTypedObject(letterboxActivityInsets, flags); } @Override @@ -428,6 +439,7 @@ public class TaskInfo { + " parentBounds=" + parentBounds + " isFocused=" + isFocused + " isVisible=" + isVisible + + " letterboxActivityInsets=" + letterboxActivityInsets + "}"; } } 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 index 6e87f131ed95a..490ef3296be62 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/letterbox/LetterboxTaskListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/letterbox/LetterboxTaskListener.java @@ -164,8 +164,6 @@ public class LetterboxTaskListener implements ShellTaskOrganizer.TaskListener { 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 += @@ -187,8 +185,6 @@ public class LetterboxTaskListener implements ShellTaskOrganizer.TaskListener { 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; @@ -209,6 +205,53 @@ public class LetterboxTaskListener implements ShellTaskOrganizer.TaskListener { + " for task: #" + taskInfo.taskId); } } + + // New bounds of the activity after it's repositioned with required gravity. + Rect newActivityBounds = new Rect(activityBounds); + // Task's surfce will be repositioned to positionInParent together with the activity + // inside it so the new activity bounds are the original activity bounds offset by + // the task's offset. + newActivityBounds.offset( + positionInParent.x - taskBounds.left, positionInParent.y - taskBounds.top); + Rect newActivityBoundsWithInsets = new Rect(newActivityBounds); + newActivityBoundsWithInsets.intersect(displayBoundsWithInsets); + // Activity handles insets on its own (e.g. under status bar or navigation bar). + // crop that is calculated above crops all insets from an activity and below insets that + // can be shown are added back to the crop bounds (e.g. if activity is still shown at the + // top of the display then the top inset won't be cropped). + // After task's surface is repositioned, intersection between an activity and insets can + // change but if it doesn't, the activity should be shown under insets to maximize visible + // area. + // Also, an activity can use area under insets and insets shouldn't be cropped in this case + // regardless of a position on the screen. + final Rect activityInsetsFromCore = taskInfo.letterboxActivityInsets; + if (newActivityBounds.top - newActivityBoundsWithInsets.top + == activityBounds.top - activityBoundsWithInsets.top + // Check whether an activity is shown under inset. If it is, then the inset from + // WM Core and the inset computed here will be different because local insets + // doesn't take into account visibility of insets requested by the activity. + || activityBoundsWithInsets.top - activityBounds.top + != activityInsetsFromCore.top) { + crop.top -= activityBoundsWithInsets.top - activityBounds.top; + } + if (newActivityBounds.bottom - newActivityBoundsWithInsets.bottom + == activityBounds.bottom - activityBoundsWithInsets.bottom + || activityBounds.bottom - activityBoundsWithInsets.bottom + != activityInsetsFromCore.bottom) { + crop.bottom += activityBounds.bottom - activityBoundsWithInsets.bottom; + } + if (newActivityBounds.left - newActivityBoundsWithInsets.left + == activityBounds.left - activityBoundsWithInsets.left + || activityBoundsWithInsets.left - activityBounds.left + != activityInsetsFromCore.left) { + crop.left -= activityBoundsWithInsets.left - activityBounds.left; + } + if (newActivityBounds.right - newActivityBoundsWithInsets.right + == activityBounds.right - activityBoundsWithInsets.right + || activityBounds.right - activityBoundsWithInsets.right + != activityInsetsFromCore.right) { + crop.right += activityBounds.right - activityBoundsWithInsets.right; + } } private Insets getInsets() { 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 index fc0e20b553d37..5cbc7d927d61d 100644 --- 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 @@ -99,7 +99,8 @@ public final class LetterboxTaskListenerTest extends ShellTestCase { /* maxBounds= */ new Rect(0, 0, 200, 100), // equal to parent bounds /* parentBounds */ new Rect(0, 0, 200, 100), /* activityBounds */ new Rect(75, 0, 125, 75), - /* taskBounds */ new Rect(50, 0, 125, 100)), + /* taskBounds */ new Rect(50, 0, 125, 100), + /* activityInsets */ new Rect(0, 0, 0, 0)), mLeash); // Task doesn't need to repositioned @@ -114,7 +115,8 @@ public final class LetterboxTaskListenerTest extends ShellTestCase { /* 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))); + /* taskBounds */ new Rect(50, 0, 125, 100), + /* activityInsets */ new Rect(0, 0, 0, 0))); // Task needs to be repositioned by 25 to the left verifySetPosition(75, 0); @@ -135,7 +137,8 @@ public final class LetterboxTaskListenerTest extends ShellTestCase { /* maxBounds= */ new Rect(0, 0, 200, 100), // equal to parent bounds /* parentBounds */ new Rect(0, 0, 200, 100), /* activityBounds */ new Rect(150, 0, 200, 75), - /* taskBounds */ new Rect(125, 0, 200, 100)), + /* taskBounds */ new Rect(125, 0, 200, 100), + /* activityInsets */ new Rect(0, 10, 10, 0)), mLeash); verifySetPosition(-15, 0); @@ -156,7 +159,8 @@ public final class LetterboxTaskListenerTest extends ShellTestCase { /* maxBounds= */ new Rect(0, 0, 200, 100), // equal to parent bounds /* parentBounds */ new Rect(0, 0, 200, 100), /* activityBounds */ new Rect(150, 0, 200, 75), - /* taskBounds */ new Rect(125, 0, 200, 100)), + /* taskBounds */ new Rect(125, 0, 200, 100), + /* activityInsets */ new Rect(0, 10, 10, 0)), mLeash); verifySetPosition(55, 0); @@ -177,7 +181,8 @@ public final class LetterboxTaskListenerTest extends ShellTestCase { /* maxBounds= */ new Rect(0, 0, 200, 100), // equal to parent bounds /* parentBounds */ new Rect(0, 0, 200, 100), /* activityBounds */ new Rect(50, 0, 100, 75), - /* taskBounds */ new Rect(25, 0, 100, 100)), + /* taskBounds */ new Rect(25, 0, 100, 100), + /* activityInsets */ new Rect(0, 10, 10, 0)), mLeash); verifySetPosition(115, 0); @@ -198,7 +203,8 @@ public final class LetterboxTaskListenerTest extends ShellTestCase { /* maxBounds= */ new Rect(0, 0, 100, 150), // equal to parent bounds /* parentBounds */ new Rect(0, 0, 100, 150), /* activityBounds */ new Rect(0, 75, 50, 125), - /* taskBounds */ new Rect(0, 50, 100, 125)), + /* taskBounds */ new Rect(0, 50, 100, 125), + /* activityInsets */ new Rect(10, 0, 0, 0)), mLeash); verifySetPosition(20, -15); @@ -219,7 +225,8 @@ public final class LetterboxTaskListenerTest extends ShellTestCase { /* maxBounds= */ new Rect(0, 0, 100, 150), // equal to parent bounds /* parentBounds */ new Rect(0, 0, 100, 150), /* activityBounds */ new Rect(0, 75, 50, 125), - /* taskBounds */ new Rect(0, 50, 100, 125)), + /* taskBounds */ new Rect(0, 50, 100, 125), + /* activityInsets */ new Rect(10, 0, 0, 0)), mLeash); verifySetPosition(20, 20); @@ -227,6 +234,29 @@ public final class LetterboxTaskListenerTest extends ShellTestCase { verifySetWindowCrop(new Rect(10, 25, 50, 75)); } + @Test + public void testOnTaskInfoAppeared_portraitWithCenterGravity_visibleLeftInset() { + 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, + /* maxBounds= */ new Rect(0, 0, 100, 150), // equal to parent bounds + /* parentBounds */ new Rect(0, 0, 100, 150), + /* activityBounds */ new Rect(0, 75, 50, 125), + /* taskBounds */ new Rect(0, 50, 100, 125), + // Activity is drawn under the left inset. + /* activityInsets */ new Rect(0, 0, 0, 0)), + mLeash); + + verifySetPosition(20, 20); + // Should return activity coordinates offset by task coordinates + verifySetWindowCrop(new Rect(0, 25, 50, 75)); + } + @Test public void testOnTaskInfoAppeared_portraitWithBottomGravity() { mLetterboxConfigController.setPortraitGravity(Gravity.BOTTOM); @@ -240,7 +270,8 @@ public final class LetterboxTaskListenerTest extends ShellTestCase { /* maxBounds= */ new Rect(0, 0, 100, 150), // equal to parent bounds /* parentBounds */ new Rect(0, 0, 100, 150), /* activityBounds */ new Rect(0, 75, 50, 125), - /* taskBounds */ new Rect(0, 50, 100, 125)), + /* taskBounds */ new Rect(0, 50, 100, 125), + /* activityInsets */ new Rect(10, 0, 0, 0)), mLeash); verifySetPosition(20, 55); @@ -261,14 +292,14 @@ public final class LetterboxTaskListenerTest extends ShellTestCase { /* maxBounds= */ new Rect(0, 0, 200, 125), // equal to parent bounds /* 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 + /* taskBounds */ new Rect(0, 0, 200, 125), + /* activityInsets */ new Rect(10, 25, 10, 10)), // 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)); + // Should return activity coordinates offset by task coordinates + verifySetWindowCrop(new Rect(15, 0, 175, 120)); } @Test @@ -284,7 +315,8 @@ public final class LetterboxTaskListenerTest extends ShellTestCase { /* maxBounds= */ new Rect(0, 0, 100, 150), /* parentBounds */ new Rect(0, 75, 100, 225), /* activityBounds */ new Rect(25, 75, 75, 125), - /* taskBounds */ new Rect(0, 75, 100, 125)), + /* taskBounds */ new Rect(0, 75, 100, 125), + /* activityInsets */ new Rect(10, 0, 0, 0)), mLeash); verifySetPosition(0, 0); @@ -295,7 +327,8 @@ public final class LetterboxTaskListenerTest extends ShellTestCase { public void testOnTaskAppeared_calledSecondTimeWithSameTaskId_throwsException() { setWindowBoundsAndInsets(new Rect(), Insets.NONE); RunningTaskInfo taskInfo = - createTaskInfo(/* taskId */ 1, new Rect(), new Rect(), new Rect(), new Rect()); + createTaskInfo(/* taskId */ 1, new Rect(), new Rect(), new Rect(), new Rect(), + new Rect()); mLetterboxTaskListener.onTaskAppeared(taskInfo, mLeash); mLetterboxTaskListener.onTaskAppeared(taskInfo, mLeash); } @@ -319,13 +352,15 @@ public final class LetterboxTaskListenerTest extends ShellTestCase { final Rect maxBounds, final Rect parentBounds, final Rect activityBounds, - final Rect taskBounds) { + final Rect taskBounds, + final Rect activityInsets) { RunningTaskInfo taskInfo = new RunningTaskInfo(); taskInfo.taskId = taskId; taskInfo.configuration.windowConfiguration.setMaxBounds(maxBounds); taskInfo.parentBounds = parentBounds; taskInfo.configuration.windowConfiguration.setBounds(taskBounds); taskInfo.letterboxActivityBounds = Rect.copyOrNull(activityBounds); + taskInfo.letterboxActivityInsets = Rect.copyOrNull(activityInsets); return taskInfo; } diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 743796b0aad09..113f4d856bdd9 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -1366,7 +1366,19 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } else if (mLetterbox != null) { mLetterbox.hide(); } - task.maybeUpdateLetterboxBounds(this, getLetterboxParams(w)); + maybeUpdateLetterboxInTaskOrganizer(w); + } + + private void maybeUpdateLetterboxInTaskOrganizer(WindowState w) { + boolean isLetterboxed = w.isLetterboxedAppWindow() && fillsParent(); + if (!isLetterboxed) { + task.maybeUpdateLetterboxInTaskOrganizer( + this, /* activityBounds= */ null, /* activityInsets= */ null); + return; + } + final Rect insets = w.getInsetsStateWithVisibilityOverride().calculateInsets( + getBounds(), Type.systemBars(), false /* ignoreVisibility */); + task.maybeUpdateLetterboxInTaskOrganizer(this, getBounds(), insets); } void updateLetterboxSurface(WindowState winHint) { @@ -1380,12 +1392,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } } - @Nullable - private Rect getLetterboxParams(WindowState w) { - boolean isLetterboxed = w.isLetterboxedAppWindow() && fillsParent(); - return isLetterboxed ? getBounds() : null; - } - Rect getLetterboxInsets() { if (mLetterbox != null) { return mLetterbox.getInsets(); diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 00f545cfad88a..8564cbc02df82 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -519,6 +519,11 @@ class Task extends WindowContainer { @Nullable private Rect mLetterboxActivityBounds; + // Activity insets if this task or its top activity is presented in letterbox mode and + // {@code null} otherwise. + @Nullable + private Rect mLetterboxActivityInsets; + // Whether the task is currently being drag-resized private boolean mDragResizing; private int mDragResizeMode; @@ -4083,6 +4088,7 @@ class Task extends WindowContainer { // bounds, e.g. fullscreen bounds instead of letterboxed bounds. To work around this, // assigning bounds from ActivityRecord#layoutLetterbox when they are ready. info.letterboxActivityBounds = Rect.copyOrNull(mLetterboxActivityBounds); + info.letterboxActivityInsets = Rect.copyOrNull(mLetterboxActivityInsets); info.positionInParent = getRelativePosition(); info.parentBounds = getParentBounds(); @@ -4110,15 +4116,17 @@ class Task extends WindowContainer { ? null : new PictureInPictureParams(rootActivity.pictureInPictureArgs); } - void maybeUpdateLetterboxBounds( - ActivityRecord activityRecord, @Nullable Rect letterboxActivityBounds) { + void maybeUpdateLetterboxInTaskOrganizer( + ActivityRecord activityRecord, + @Nullable Rect activityBounds, + @Nullable Rect activityInsets) { if (isOrganized() && mReuseActivitiesReport.top == activityRecord // Want to force update only if letterbox bounds have changed. - && !Objects.equals( - mLetterboxActivityBounds, - letterboxActivityBounds)) { - mLetterboxActivityBounds = Rect.copyOrNull(letterboxActivityBounds); + && (!Objects.equals(mLetterboxActivityBounds, activityBounds) + || !Objects.equals(mLetterboxActivityInsets, activityInsets))) { + mLetterboxActivityBounds = Rect.copyOrNull(activityBounds); + mLetterboxActivityInsets = Rect.copyOrNull(activityInsets); // Forcing update to reduce visual jank during the transition. dispatchTaskInfoChangedIfNeeded(true /* force */); }