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

Commit 4f13d951 authored by Massimo Carli's avatar Massimo Carli
Browse files

[32/n] Fix Insets for Letterbox in Shell

Each AppCompatLetterboxPolicyState implementation contains
the information related to the letterbox surfaces bounds.
The letterbox surfaces bounds information was missing in
case of the Letterbox implmentation in shell so the
logic related to status bar opacity was broken when the
flag was enabled.

The common logic to understand if the letterbox surfaces
overlap the bar has now been moved to AppCompatLetterboxUtils.

Flag: com.android.window.flags.app_compat_refactoring
Bug: 407731267
Test: atest WmTests:AppCompatLetterboxUtilsTest
Test: atest WmTests:SizeCompatTests

Change-Id: Ibe0a4460886c17c1dd57a2d6de16f53c77ddf730
parent 7e858498
Loading
Loading
Loading
Loading
+31 −11
Original line number Diff line number Diff line
@@ -480,6 +480,15 @@ class AppCompatLetterboxPolicy {
        private final Point mLetterboxPosition = new Point();
        private boolean mRunning;

        // The model needs to store the bounds for the multiple surfaces which will be
        // created in Shell anyway.
        private final Rect mLeftBounds = new Rect();
        private final Rect mTopBounds = new Rect();
        private final Rect mRightBounds = new Rect();
        private final Rect mBottomBounds = new Rect();
        private final Rect[] mSurfacesBounds =
                new Rect[]{mLeftBounds, mTopBounds, mRightBounds, mBottomBounds};

        @Override
        public void layoutLetterboxIfNeeded(@NonNull WindowState w) {
            mRunning = true;
@@ -488,6 +497,7 @@ class AppCompatLetterboxPolicy {
            calculateLetterboxInnerBounds(mActivityRecord, w, mInnerBounds);
            mActivityRecord.mAppCompatController.getReachabilityPolicy()
                    .setLetterboxInnerBoundsSupplier(() -> mInnerBounds);
            updateSurfacesBounds();
        }

        @Override
@@ -528,10 +538,10 @@ class AppCompatLetterboxPolicy {
        public Rect getLetterboxInsets() {
            if (isRunning()) {
                return new Rect(
                        Math.max(0, mInnerBounds.left - mOuterBounds.left),
                        Math.max(0, mOuterBounds.top - mInnerBounds.top),
                        Math.max(0, mOuterBounds.right - mInnerBounds.right),
                        Math.max(0, mInnerBounds.bottom - mOuterBounds.bottom)
                        Math.max(0, mLeftBounds.width()),
                        Math.max(0, mTopBounds.height()),
                        Math.max(0, mRightBounds.width()),
                        Math.max(0, mBottomBounds.height())
                );
            }
            return new Rect();
@@ -570,15 +580,25 @@ class AppCompatLetterboxPolicy {
            start(winHint);
        }

        /**
         * @return {@code true} if bar shown within a given rectangle is allowed to be fully
         *          transparent when the current activity is displayed.
         */
        @Override
        public boolean isFullyTransparentBarAllowed(@NonNull Rect rect) {
            // TODO(b/374921442) Handle Transparent Activities Letterboxing in Shell.
            // At the moment Shell handles letterbox with a single surface. This would make
            // notIntersectsOrFullyContains() to return false in the existing Letterbox
            // implementation.
            // Note: Previous implementation is
            //       !isRunning() || mLetterbox.notIntersectsOrFullyContains(rect);
            return !isRunning();
            return !isRunning() || AppCompatLetterboxUtils.fullyContainsOrNotIntersects(rect,
                    mSurfacesBounds);
        }

        private void updateSurfacesBounds() {
            mTopBounds.set(mOuterBounds.left, mOuterBounds.top, mOuterBounds.right,
                    mInnerBounds.top);
            mLeftBounds.set(mOuterBounds.left, mOuterBounds.top, mInnerBounds.left,
                    mOuterBounds.bottom);
            mRightBounds.set(mInnerBounds.right, mOuterBounds.top, mOuterBounds.right,
                    mOuterBounds.bottom);
            mBottomBounds.set(mOuterBounds.left, mInnerBounds.bottom, mOuterBounds.right,
                    mOuterBounds.bottom);
        }
    }
}
+27 −0
Original line number Diff line number Diff line
@@ -99,4 +99,31 @@ class AppCompatLetterboxUtils {
        outInnerBounds.set(
                transparentPolicy.isRunning() ? activity.getBounds() : window.getFrame());
    }


    /**
     * Returns {@code true} if the letterbox does not overlap with the bar, or the letterbox can
     * fully cover the window frame.
     *
     * @param rect             The area of the window frame.
     * @param boundsToCheck A Collection of bounds to check.
     */
    static boolean fullyContainsOrNotIntersects(@NonNull Rect rect, @NonNull Rect[] boundsToCheck) {
        // TODO(b/409293223): Make this algorithm simpler and more efficient.
        int emptyCount = 0;
        int noOverlappingCount = 0;
        for (Rect bounds : boundsToCheck) {
            if (bounds.isEmpty()) {
                // empty letterbox
                emptyCount++;
            } else if (!Rect.intersects(bounds, rect)) {
                // no overlapping
                noOverlappingCount++;
            } else if (bounds.contains(rect)) {
                // overlapping and covered
                return true;
            }
        }
        return (emptyCount + noOverlappingCount) == boundsToCheck.length;
    }
}
+5 −16
Original line number Diff line number Diff line
@@ -68,6 +68,7 @@ public class Letterbox {
    // for overlaping an app window and letterbox surfaces.
    private final LetterboxSurface mFullWindowSurface = new LetterboxSurface("fullWindow");
    private final LetterboxSurface[] mSurfaces = { mLeft, mTop, mRight, mBottom };
    private final Rect[] mTmpSurfacesRect = new Rect[4];

    @NonNull
    private final AppCompatReachabilityPolicy mAppCompatReachabilityPolicy;
@@ -138,23 +139,11 @@ public class Letterbox {
     *
     * @param rect The area of the window frame.
     */
    boolean notIntersectsOrFullyContains(Rect rect) {
        int emptyCount = 0;
        int noOverlappingCount = 0;
        for (LetterboxSurface surface : mSurfaces) {
            final Rect surfaceRect = surface.mLayoutFrameGlobal;
            if (surfaceRect.isEmpty()) {
                // empty letterbox
                emptyCount++;
            } else if (!Rect.intersects(surfaceRect, rect)) {
                // no overlapping
                noOverlappingCount++;
            } else if (surfaceRect.contains(rect)) {
                // overlapping and covered
                return true;
            }
    boolean notIntersectsOrFullyContains(@NonNull Rect rect) {
        for (int i = 0; i < mTmpSurfacesRect.length; i++) {
            mTmpSurfacesRect[i] = mSurfaces[i].mLayoutFrameGlobal;
        }
        return (emptyCount + noOverlappingCount) == mSurfaces.length;
        return AppCompatLetterboxUtils.fullyContainsOrNotIntersects(rect, mTmpSurfacesRect);
    }

    /**
+139 −0
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.server.wm.AppCompatLetterboxUtils.calculateLetterboxInnerBounds;
import static com.android.server.wm.AppCompatLetterboxUtils.calculateLetterboxOuterBounds;
import static com.android.server.wm.AppCompatLetterboxUtils.calculateLetterboxPosition;
import static com.android.server.wm.AppCompatLetterboxUtils.fullyContainsOrNotIntersects;

import static org.mockito.Mockito.mock;

@@ -143,6 +144,123 @@ public class AppCompatLetterboxUtilsTest extends WindowTestsBase {
        });
    }

    @Test
    public void testNoBoundsToCheck() {
        runTestScenario((robot) -> {
            robot.setWindowFrameArea(/* left */ 0, /* top */ 0, /* right */ 10, /* bottom */ 10);
            robot.setBoundsToCheck();
            robot.checkFullyContainsOrNotIntersects(/* expected */ true);
        });
    }

    @Test
    public void testEmptyBoundsToCheck() {
        runTestScenario((robot) -> {
            robot.setWindowFrameArea(/* left */ 0, /* top */ 0, /* right */ 10, /* bottom */ 10);
            robot.setBoundsToCheck(new Rect(10, 10, 20, 20), new Rect(30, 30, 40, 40));
            robot.checkFullyContainsOrNotIntersects(/* expected */ true);
        });
    }

    @Test
    public void testContainsEmptyRect() {
        runTestScenario((robot) -> {
            robot.setWindowFrameArea(/* left */ 0, /* top */ 0, /* right */ 10, /* bottom */ 10);
            robot.setBoundsToCheck(new Rect(10, 10, 20, 20), new Rect());
            robot.checkFullyContainsOrNotIntersects(/* expected */ true);
        });
    }

    @Test
    public void testCheckFullyContainsOrNotIntersects_NoIntersection() {
        runTestScenario((robot) -> {
            robot.setWindowFrameArea(/* left */ 0, /* top */ 0, /* right */ 10, /* bottom */ 10);
            robot.setBoundsToCheck(new Rect(10, 10, 20, 20));
            robot.checkFullyContainsOrNotIntersects(/* expected */ true);
        });
    }

    @Test
    public void testCheckFullyContainsOrNotIntersects_FullyContains() {
        runTestScenario((robot) -> {
            robot.setWindowFrameArea(/* left */ 0, /* top */ 0, /* right */ 10, /* bottom */ 10);
            robot.setBoundsToCheck(new Rect(-5, -5, 15, 15));
            robot.checkFullyContainsOrNotIntersects(/* expected */ true);
        });
    }

    @Test
    public void testCheckFullyContainsOrNotIntersects_PartiallyIntersects() {
        runTestScenario((robot) -> {
            robot.setWindowFrameArea(/* left */ 0, /* top */ 0, /* right */ 10, /* bottom */ 10);
            robot.setBoundsToCheck(new Rect(5, 5, 15, 15));
            robot.checkFullyContainsOrNotIntersects(/* expected */ false);
        });
    }

    @Test
    public void testCheckFullyContainsOrNotIntersects_MultipleBoundsNoIntersection() {
        runTestScenario((robot) -> {
            robot.setWindowFrameArea(/* left */ 0, /* top */ 0, /* right */ 10, /* bottom */ 10);
            robot.setBoundsToCheck(new Rect(10, 10, 20, 20), new Rect(-20, -20, -10, -10));
            robot.checkFullyContainsOrNotIntersects(/* expected */ true);
        });
    }

    @Test
    public void testCheckFullyContainsOrNotIntersects_MultipleBoundsWithOneContaining() {
        runTestScenario((robot) -> {
            robot.setWindowFrameArea(/* left */ 0, /* top */ 0, /* right */ 10, /* bottom */ 10);
            robot.setBoundsToCheck(new Rect(10, 10, 20, 20), new Rect(-5, -5, 15, 15));
            robot.checkFullyContainsOrNotIntersects(/* expected */ true);
        });
    }

    @Test
    public void testCheckFullyContainsOrNotIntersects_MultipleBoundsWithOneIntersecting() {
        runTestScenario((robot) -> {
            robot.setWindowFrameArea(/* left */ 0, /* top */ 0, /* right */ 10, /* bottom */ 10);
            robot.setBoundsToCheck(new Rect(10, 10, 20, 20), new Rect(5, 5, 15, 15));
            robot.checkFullyContainsOrNotIntersects(/* expected */ false);
        });
    }

    @Test
    public void testCheckFullyContainsOrNotIntersects_MultipleBoundsWithEmptyAndNoIntersection() {
        runTestScenario((robot) -> {
            robot.setWindowFrameArea(/* left */ 0, /* top */ 0, /* right */ 10, /* bottom */ 10);
            robot.setBoundsToCheck(new Rect(), new Rect(10, 10, 20, 20));
            robot.checkFullyContainsOrNotIntersects(/* expected */ true);
        });
    }

    @Test
    public void testCheckFullyContainsOrNotIntersects_MultipleBoundsWithEmptyAndContaining() {
        runTestScenario((robot) -> {
            robot.setWindowFrameArea(/* left */ 0, /* top */ 0, /* right */ 10, /* bottom */ 10);
            robot.setBoundsToCheck(new Rect(), new Rect(-5, -5, 15, 15));
            robot.checkFullyContainsOrNotIntersects(/* expected */ true);
        });
    }

    @Test
    public void testCheckFullyContainsOrNotIntersects_EmptyRectToCheck() {
        runTestScenario((robot) -> {
            robot.setWindowFrameArea(new Rect());
            robot.setBoundsToCheck(new Rect(10, 10, 20, 20), new Rect(-5, -5, 15, 15));
            robot.checkFullyContainsOrNotIntersects(/* expected */ true);
        });
    }

    @Test
    public void testCheckFullyContainsOrNotIntersects_EmptyRectToCheckAndEmptyBounds() {
        runTestScenario((robot) -> {
            robot.setWindowFrameArea(new Rect());
            robot.setBoundsToCheck(new Rect());
            robot.checkFullyContainsOrNotIntersects(/* expected */ true);
        });
    }

    /**
     * Runs a test scenario providing a Robot.
     */
@@ -157,6 +275,10 @@ public class AppCompatLetterboxUtilsTest extends WindowTestsBase {
        private final Rect mInnerBound = new Rect();
        private final Rect mOuterBound = new Rect();

        private final Rect mWindowFrameArea = new Rect();

        private Rect[] mBoundsToCheck;

        @NonNull
        private final WindowState mWindowState;

@@ -188,6 +310,18 @@ public class AppCompatLetterboxUtilsTest extends WindowTestsBase {
            doReturn(frame).when(mWindowState).getFrame();
        }

        void setWindowFrameArea(int left, int top, int right, int bottom) {
            mWindowFrameArea.set(left, top, right, bottom);
        }

        void setWindowFrameArea(@NonNull Rect windowFrameArea) {
            mWindowFrameArea.set(windowFrameArea);
        }

        void setBoundsToCheck(@NonNull Rect... boundsToCheck) {
            mBoundsToCheck = boundsToCheck;
        }

        void getLetterboxPosition() {
            calculateLetterboxPosition(activity().top(), mPosition);
        }
@@ -235,5 +369,10 @@ public class AppCompatLetterboxUtilsTest extends WindowTestsBase {
            Assert.assertEquals(mInnerBound, activity().top().getBounds());
        }

        void checkFullyContainsOrNotIntersects(boolean expected) {
            Assert.assertEquals(expected,
                    fullyContainsOrNotIntersects(mWindowFrameArea, mBoundsToCheck));
        }

    }
}