Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit c022f353 authored by Vali Calinescu's avatar Vali Calinescu
Browse files

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
parent 84e2c20b
Loading
Loading
Loading
Loading
+22 −23
Original line number Diff line number Diff line
@@ -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);
    }
@@ -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;
    }

+79 −6
Original line number Diff line number Diff line
@@ -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) {
+15 −2
Original line number Diff line number Diff line
@@ -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;