From 41eff6820e470401a1b8fe74e55bef6c84bcaeb7 Mon Sep 17 00:00:00 2001 From: Mariia Sandrikova Date: Mon, 7 Dec 2020 22:17:41 +0000 Subject: [PATCH] Letterbox: show activity under insets when possible or needed Activity handles insets on its own (e.g. under status bar or navigation bar). 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. Test: atest LetterboxTaskListenerTest Fix: 174086576 Change-Id: I84cd28520ead3fb0099f15cc672167871c7d78a1 --- core/java/android/app/TaskInfo.java | 14 +++- .../letterbox/LetterboxTaskListener.java | 51 +++++++++++++-- .../letterbox/LetterboxTaskListenerTest.java | 65 ++++++++++++++----- .../com/android/server/wm/ActivityRecord.java | 20 ++++-- .../core/java/com/android/server/wm/Task.java | 20 ++++-- 5 files changed, 137 insertions(+), 33 deletions(-) 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 */); }