From c022f353c5555d7253cecba35b71d9040bf21071 Mon Sep 17 00:00:00 2001 From: Vali Calinescu Date: Wed, 29 Jun 2022 22:19:18 +0000 Subject: [PATCH] Fix display cutout vertical centering bug The current way of computing bounds inside `updateResolvedBoundsPosition` is broken because it can result in negative results for the top bounds if the aspect ratio restriction makes the bounds of the app larger than parentAppBounds. We are fixing this by matching the parentBounds inside `applyAspecRatio` if we find out that the new bounds computed here overlap with the parentAppBounds. We also found out that the vertical and horizontal insets have separate treatment for the aspect ratio restriction, so we decided to cut both insets inside `applyAspectRatio` and only add back the vertical insets inside `updateResolvedBoundsPosition` if the top of the appBounds aligns with the top of parentAppBounds so that the app content gets aligned with the status bar. Bug: 236226770 Test: atest WmTests:SizeCompatTests Change-Id: I183b5dded5276b50f6f8a2d56c09774ab76818a4 --- .../com/android/server/wm/ActivityRecord.java | 45 +++++----- .../android/server/wm/SizeCompatTests.java | 85 +++++++++++++++++-- .../android/server/wm/TestDisplayContent.java | 17 +++- 3 files changed, 116 insertions(+), 31 deletions(-) diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 4c0a0171024a9..2dbeee96da637 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -8042,11 +8042,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // Horizontal position int offsetX = 0; if (parentBounds.width() != screenResolvedBounds.width()) { - if (screenResolvedBounds.width() >= parentAppBounds.width()) { - // If resolved bounds overlap with insets, center within app bounds. - offsetX = getCenterOffset( - parentAppBounds.width(), screenResolvedBounds.width()); - } else { + if (screenResolvedBounds.width() <= parentAppBounds.width()) { float positionMultiplier = mLetterboxUiController.getHorizontalPositionMultiplier( newParentConfiguration); @@ -8058,11 +8054,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // Vertical position int offsetY = 0; if (parentBounds.height() != screenResolvedBounds.height()) { - if (screenResolvedBounds.height() >= parentAppBounds.height()) { - // If resolved bounds overlap with insets, center within app bounds. - offsetY = getCenterOffset( - parentAppBounds.height(), screenResolvedBounds.height()); - } else { + if (screenResolvedBounds.height() <= parentAppBounds.height()) { float positionMultiplier = mLetterboxUiController.getVerticalPositionMultiplier( newParentConfiguration); @@ -8080,6 +8072,15 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A offsetBounds(resolvedConfig, offsetX, offsetY); } + // If the top is aligned with parentAppBounds add the vertical insets back so that the app + // content aligns with the status bar + if (resolvedConfig.windowConfiguration.getAppBounds().top == parentAppBounds.top) { + resolvedConfig.windowConfiguration.getBounds().top = parentBounds.top; + if (mSizeCompatBounds != null) { + mSizeCompatBounds.top = parentBounds.top; + } + } + // Since bounds has changed, the configuration needs to be computed accordingly. getTaskFragment().computeConfigResourceOverrides(resolvedConfig, newParentConfiguration); } @@ -8457,7 +8458,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // Above coordinates are in "@" space, now place "*" and "#" to screen space. final boolean fillContainer = resolvedBounds.equals(containingBounds); final int screenPosX = fillContainer ? containerBounds.left : containerAppBounds.left; - final int screenPosY = fillContainer ? containerBounds.top : containerAppBounds.top; + final int screenPosY = fillContainer ? containerBounds.top : containerAppBounds.top; if (screenPosX != 0 || screenPosY != 0) { if (mSizeCompatBounds != null) { @@ -8803,24 +8804,22 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // Also account for the insets (e.g. display cutouts, navigation bar), which will be // clipped away later in {@link Task#computeConfigResourceOverrides()}, i.e., the out // bounds are the app bounds restricted by aspect ratio + clippable insets. Otherwise, - // the app bounds would end up too small. + // the app bounds would end up too small. To achieve this we will also add clippable insets + // when the corresponding dimension fully fills the parent + int right = activityWidth + containingAppBounds.left; + int left = containingAppBounds.left; if (right >= containingAppBounds.right) { - right += containingBounds.right - containingAppBounds.right; + right = containingBounds.right; + left = containingBounds.left; } int bottom = activityHeight + containingAppBounds.top; + int top = containingAppBounds.top; if (bottom >= containingAppBounds.bottom) { - bottom += containingBounds.bottom - containingAppBounds.bottom; + bottom = containingBounds.bottom; + top = containingBounds.top; } - outBounds.set(containingBounds.left, containingBounds.top, right, bottom); - - // If the bounds are restricted by fixed aspect ratio, then out bounds should be put in the - // container app bounds. Otherwise the entire container bounds are available. - if (!outBounds.equals(containingBounds)) { - // The horizontal position should not cover insets (e.g. display cutout). - outBounds.left = containingAppBounds.left; - } - + outBounds.set(left, top, right, bottom); return true; } diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java index 60d3f10bb103f..49076413779c9 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -210,10 +210,8 @@ public class SizeCompatTests extends WindowTestsBase { assertFitted(); // After the orientation of activity is changed, the display is rotated, the aspect - // ratio should be the same (bounds=[100, 0 - 800, 583], appBounds=[100, 0 - 800, 583]). + // ratio should be the same (bounds=[0, 0 - 800, 583], appBounds=[100, 0 - 800, 583]). assertEquals(appBounds.width(), appBounds.height() * aspectRatio, 0.5f /* delta */); - // The notch is no longer on top. - assertEquals(appBounds, mActivity.getBounds()); // Activity max bounds are sandboxed. assertActivityMaxBoundsSandboxed(); @@ -467,8 +465,6 @@ public class SizeCompatTests extends WindowTestsBase { assertEquals(ROTATION_270, mTask.getWindowConfiguration().getRotation()); assertEquals(origBounds.width(), currentBounds.width()); - // The notch is on horizontal side, so current height changes from 1460 to 1400. - assertEquals(origBounds.height() - notchHeight, currentBounds.height()); // Make sure the app size is the same assertEquals(origAppBounds.width(), appBounds.width()); assertEquals(origAppBounds.height(), appBounds.height()); @@ -2354,7 +2350,7 @@ public class SizeCompatTests extends WindowTestsBase { mActivity.mRootWindowContainer.performSurfacePlacement(); Rect mBounds = new Rect(mActivity.getWindowConfiguration().getBounds()); - assertEquals(mBounds, new Rect(0, 750, 1000, 1950)); + assertEquals(mBounds, new Rect(0, 900, 1000, 2000)); DisplayPolicy displayPolicy = mActivity.getDisplayContent().getDisplayPolicy(); LetterboxDetails[] expectedLetterboxDetails = {new LetterboxDetails( @@ -2533,6 +2529,64 @@ public class SizeCompatTests extends WindowTestsBase { assertEquals(sizeCompatScaled, mActivity.getBounds()); } + @Test + public void testApplyAspectRatio_activityAlignWithParentAppVertical() { + // The display's app bounds will be (0, 100, 1000, 2350) + final DisplayContent display = new TestDisplayContent.Builder(mAtm, 1000, 2500) + .setCanRotate(false) + .setCutout(0, 100, 0, 150) + .build(); + + setUpApp(display); + prepareUnresizable(mActivity, 2.1f /* maxAspect */, SCREEN_ORIENTATION_UNSPECIFIED); + // The activity height is 2100 and the display's app bounds height is 2250, so the activity + // can be aligned inside parentAppBounds + assertEquals(mActivity.getBounds(), new Rect(0, 0, 1000, 2200)); + } + @Test + public void testApplyAspectRatio_activityCannotAlignWithParentAppVertical() { + // The display's app bounds will be (0, 100, 1000, 2150) + final DisplayContent display = new TestDisplayContent.Builder(mAtm, 1000, 2300) + .setCanRotate(false) + .setCutout(0, 100, 0, 150) + .build(); + + setUpApp(display); + prepareUnresizable(mActivity, 2.1f /* maxAspect */, SCREEN_ORIENTATION_UNSPECIFIED); + // The activity height is 2100 and the display's app bounds height is 2050, so the activity + // cannot be aligned inside parentAppBounds and it will fill the parentBounds of the display + assertEquals(mActivity.getBounds(), display.getBounds()); + } + + @Test + public void testApplyAspectRatio_activityAlignWithParentAppHorizontal() { + // The display's app bounds will be (100, 0, 2350, 1000) + final DisplayContent display = new TestDisplayContent.Builder(mAtm, 2500, 1000) + .setCanRotate(false) + .setCutout(100, 0, 150, 0) + .build(); + + setUpApp(display); + prepareUnresizable(mActivity, 2.1f /* maxAspect */, SCREEN_ORIENTATION_UNSPECIFIED); + // The activity width is 2100 and the display's app bounds width is 2250, so the activity + // can be aligned inside parentAppBounds + assertEquals(mActivity.getBounds(), new Rect(175, 0, 2275, 1000)); + } + @Test + public void testApplyAspectRatio_activityCannotAlignWithParentAppHorizontal() { + // The display's app bounds will be (100, 0, 2150, 1000) + final DisplayContent display = new TestDisplayContent.Builder(mAtm, 2300, 1000) + .setCanRotate(false) + .setCutout(100, 0, 150, 0) + .build(); + + setUpApp(display); + prepareUnresizable(mActivity, 2.1f /* maxAspect */, SCREEN_ORIENTATION_UNSPECIFIED); + // The activity width is 2100 and the display's app bounds width is 2050, so the activity + // cannot be aligned inside parentAppBounds and it will fill the parentBounds of the display + assertEquals(mActivity.getBounds(), display.getBounds()); + } + @Test public void testUpdateResolvedBoundsHorizontalPosition_activityFillParentWidth() { // When activity width equals parent width, multiplier shouldn't have any effect. @@ -2608,6 +2662,25 @@ public class SizeCompatTests extends WindowTestsBase { /* sizeCompatScaled */ new Rect(0, 1050, 700, 1400)); } + @Test + public void testUpdateResolvedBoundsPosition_alignToTop() { + final int notchHeight = 100; + final DisplayContent display = new TestDisplayContent.Builder(mAtm, 1000, 2800) + .setNotch(notchHeight) + .build(); + setUpApp(display); + + // Prepare unresizable activity with max aspect ratio + prepareUnresizable(mActivity, /* maxAspect */ 1.1f, SCREEN_ORIENTATION_UNSPECIFIED); + + Rect mBounds = new Rect(mActivity.getWindowConfiguration().getBounds()); + Rect appBounds = new Rect(mActivity.getWindowConfiguration().getAppBounds()); + // The insets should be cut for aspect ratio and then added back because the appBounds + // are aligned to the top of the parentAppBounds + assertEquals(mBounds, new Rect(0, 0, 1000, 1200)); + assertEquals(appBounds, new Rect(0, notchHeight, 1000, 1200)); + } + private void assertVerticalPositionForDifferentDisplayConfigsForLandscapeActivity( float letterboxVerticalPositionMultiplier, Rect fixedOrientationLetterbox, Rect sizeCompatUnscaled, Rect sizeCompatScaled) { diff --git a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java index aa3ca18073c3c..bf1d1fa982492 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java +++ b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java @@ -143,11 +143,24 @@ class TestDisplayContent extends DisplayContent { mInfo.ownerUid = ownerUid; return this; } - Builder setNotch(int height) { + Builder setCutout(int left, int top, int right, int bottom) { + final int cutoutFillerSize = 80; + Rect boundLeft = left != 0 ? new Rect(0, 0, left, cutoutFillerSize) : null; + Rect boundTop = top != 0 ? new Rect(0, 0, cutoutFillerSize, top) : null; + Rect boundRight = right != 0 ? new Rect(mInfo.logicalWidth - right, 0, + mInfo.logicalWidth, cutoutFillerSize) : null; + Rect boundBottom = bottom != 0 + ? new Rect(0, mInfo.logicalHeight - bottom, cutoutFillerSize, + mInfo.logicalHeight) : null; + mInfo.displayCutout = new DisplayCutout( - Insets.of(0, height, 0, 0), null, new Rect(20, 0, 80, height), null, null); + Insets.of(left, top, right, bottom), + boundLeft, boundTop, boundRight, boundBottom); return this; } + Builder setNotch(int height) { + return setCutout(0, height, 0, 0); + } Builder setStatusBarHeight(int height) { mStatusBarHeight = height; return this;