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

Commit dcc92eda authored by Adrian Roos's avatar Adrian Roos
Browse files

Adjust window layout for DisplayCutout

Add policy around how the display cutout should influence window layout:
- if not requested, windows should not overlap with the display cutout
 - except windows that asked for LAYOUT_IN_SCREEN | LAYOUT_INSET_DECOR,
   which overlap only with a top cutout, provided they did not request any FULLSCREEN mode.
- the content frame must never overlap with the display cutout
- adds FLAG2_LAYOUT_IN_CUTOUT to explicitly ask to be laid out in the cutout area.

Bug: 65689439
Test: atest com.android.server.policy.PhoneWindowManagerLayoutTest
Change-Id: I3a966bc78ef7a4e076104a996799369c60ab7de1
parent 000cf5e0
Loading
Loading
Loading
Loading
+29 −0
Original line number Diff line number Diff line
@@ -1268,6 +1268,35 @@ public interface WindowManager extends ViewManager {
        }, formatToHexString = true)
        public int flags;

        /** @hide */
        @Retention(RetentionPolicy.SOURCE)
        @IntDef(
            flag = true,
            value = {
                    LayoutParams.FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA,
            })
        @interface Flags2 {}

        /**
         * Window flag: allow placing the window within the area that overlaps with the
         * display cutout.
         *
         * <p>
         * The window must correctly position its contents to take the display cutout into account.
         *
         * @see DisplayCutout
         * @hide for now
         */
        public static final long FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA = 0x00000001;

        /**
         * Various behavioral options/flags.  Default is none.
         *
         * @see #FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA
         * @hide for now
         */
        @Flags2 public long flags2;

        /**
         * If the window has requested hardware acceleration, but this is not
         * allowed in the process it is in, then still render it as if it is
+56 −10
Original line number Diff line number Diff line
@@ -48,6 +48,7 @@ import static android.view.WindowManager.INPUT_CONSUMER_NAVIGATION;
import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;
import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;
import static android.view.WindowManager.LayoutParams.FIRST_SYSTEM_WINDOW;
import static android.view.WindowManager.LayoutParams.FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA;
import static android.view.WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON;
import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
import static android.view.WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN;
@@ -4515,6 +4516,14 @@ public class PhoneWindowManager implements WindowManagerPolicy {
            }
        }
        layoutScreenDecorWindows(displayFrames, pf, df, dcf);

        if (displayFrames.mDisplayCutoutSafe.top > displayFrames.mUnrestricted.top) {
            // Make sure that the zone we're avoiding for the cutout is at least as tall as the
            // status bar; otherwise fullscreen apps will end up cutting halfway into the status
            // bar.
            displayFrames.mDisplayCutoutSafe.top = Math.max(displayFrames.mDisplayCutoutSafe.top,
                    displayFrames.mStable.top);
        }
    }

    private void layoutScreenDecorWindows(DisplayFrames displayFrames, Rect pf, Rect df, Rect dcf) {
@@ -4650,11 +4659,15 @@ public class PhoneWindowManager implements WindowManagerPolicy {
        final Rect dockFrame = displayFrames.mDock;
        mNavigationBarPosition = navigationBarPosition(displayWidth, displayHeight, rotation);

        final Rect cutoutSafeUnrestricted = mTmpRect;
        cutoutSafeUnrestricted.set(displayFrames.mUnrestricted);
        cutoutSafeUnrestricted.intersectUnchecked(displayFrames.mDisplayCutoutSafe);

        if (mNavigationBarPosition == NAV_BAR_BOTTOM) {
            // It's a system nav bar or a portrait screen; nav bar goes on bottom.
            final int top = displayFrames.mUnrestricted.bottom
            final int top = cutoutSafeUnrestricted.bottom
                    - getNavigationBarHeight(rotation, uiMode);
            mTmpNavigationFrame.set(0, top, displayWidth, displayFrames.mUnrestricted.bottom);
            mTmpNavigationFrame.set(0, top, displayWidth, cutoutSafeUnrestricted.bottom);
            displayFrames.mStable.bottom = displayFrames.mStableFullscreen.bottom = top;
            if (transientNavBarShowing) {
                mNavigationBarController.setBarShowingLw(true);
@@ -4675,9 +4688,9 @@ public class PhoneWindowManager implements WindowManagerPolicy {
            }
        } else if (mNavigationBarPosition == NAV_BAR_RIGHT) {
            // Landscape screen; nav bar goes to the right.
            final int left = displayFrames.mUnrestricted.right
            final int left = cutoutSafeUnrestricted.right
                    - getNavigationBarWidth(rotation, uiMode);
            mTmpNavigationFrame.set(left, 0, displayFrames.mUnrestricted.right, displayHeight);
            mTmpNavigationFrame.set(left, 0, cutoutSafeUnrestricted.right, displayHeight);
            displayFrames.mStable.right = displayFrames.mStableFullscreen.right = left;
            if (transientNavBarShowing) {
                mNavigationBarController.setBarShowingLw(true);
@@ -4698,9 +4711,9 @@ public class PhoneWindowManager implements WindowManagerPolicy {
            }
        } else if (mNavigationBarPosition == NAV_BAR_LEFT) {
            // Seascape screen; nav bar goes to the left.
            final int right = displayFrames.mUnrestricted.left
            final int right = cutoutSafeUnrestricted.left
                    + getNavigationBarWidth(rotation, uiMode);
            mTmpNavigationFrame.set(displayFrames.mUnrestricted.left, 0, right, displayHeight);
            mTmpNavigationFrame.set(cutoutSafeUnrestricted.left, 0, right, displayHeight);
            displayFrames.mStable.left = displayFrames.mStableFullscreen.left = right;
            if (transientNavBarShowing) {
                mNavigationBarController.setBarShowingLw(true);
@@ -4852,6 +4865,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {

        final int type = attrs.type;
        final int fl = PolicyControl.getWindowFlags(win, attrs);
        final long fl2 = attrs.flags2;
        final int pfl = attrs.privateFlags;
        final int sim = attrs.softInputMode;
        final int requestedSysUiFl = PolicyControl.getSystemUiVisibility(win, null);
@@ -4872,6 +4886,14 @@ public class PhoneWindowManager implements WindowManagerPolicy {

        final int adjust = sim & SOFT_INPUT_MASK_ADJUST;

        final boolean requestedFullscreen = (fl & FLAG_FULLSCREEN) != 0
                || (requestedSysUiFl & View.SYSTEM_UI_FLAG_FULLSCREEN) != 0
                || (requestedSysUiFl & View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) != 0;

        final boolean layoutInScreen = (fl & FLAG_LAYOUT_IN_SCREEN) == FLAG_LAYOUT_IN_SCREEN;
        final boolean layoutInsetDecor = (fl & FLAG_LAYOUT_INSET_DECOR) == FLAG_LAYOUT_INSET_DECOR;
        final boolean layoutInCutout = (fl2 & FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA) != 0;

        sf.set(displayFrames.mStable);

        if (type == TYPE_INPUT_METHOD) {
@@ -4881,7 +4903,8 @@ public class PhoneWindowManager implements WindowManagerPolicy {
            df.set(displayFrames.mDock);
            pf.set(displayFrames.mDock);
            // IM dock windows layout below the nav bar...
            pf.bottom = df.bottom = of.bottom = displayFrames.mUnrestricted.bottom;
            pf.bottom = df.bottom = of.bottom = Math.min(displayFrames.mUnrestricted.bottom,
                    displayFrames.mDisplayCutoutSafe.bottom);
            // ...with content insets above the nav bar
            cf.bottom = vf.bottom = displayFrames.mStable.bottom;
            if (mStatusBar != null && mFocusedWindow == mStatusBar && canReceiveInput(mStatusBar)) {
@@ -4952,8 +4975,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
                }
            }

            if ((fl & (FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR))
                    == (FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR)) {
            if (layoutInScreen && layoutInsetDecor) {
                if (DEBUG_LAYOUT) Slog.v(TAG, "layoutWindowLw(" + attrs.getTitle()
                            + "): IN_SCREEN, INSET_DECOR");
                // This is the case for a normal activity window: we want it to cover all of the
@@ -5030,6 +5052,9 @@ public class PhoneWindowManager implements WindowManagerPolicy {
                        // moving from a window that is not hiding the status bar to one that is.
                        cf.set(displayFrames.mRestricted);
                    }
                    if (requestedFullscreen && !layoutInCutout) {
                        pf.intersectUnchecked(displayFrames.mDisplayCutoutSafe);
                    }
                    applyStableConstraints(sysUiFl, fl, cf, displayFrames);
                    if (adjust != SOFT_INPUT_ADJUST_NOTHING) {
                        vf.set(displayFrames.mCurrent);
@@ -5037,7 +5062,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
                        vf.set(cf);
                    }
                }
            } else if ((fl & FLAG_LAYOUT_IN_SCREEN) != 0 || (sysUiFl
            } else if (layoutInScreen || (sysUiFl
                    & (View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                            | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION)) != 0) {
                if (DEBUG_LAYOUT) Slog.v(TAG, "layoutWindowLw(" + attrs.getTitle()
@@ -5115,6 +5140,9 @@ public class PhoneWindowManager implements WindowManagerPolicy {
                    of.set(displayFrames.mUnrestricted);
                    df.set(displayFrames.mUnrestricted);
                    pf.set(displayFrames.mUnrestricted);
                    if (requestedFullscreen && !layoutInCutout) {
                        pf.intersectUnchecked(displayFrames.mDisplayCutoutSafe);
                    }
                } else if ((sysUiFl & View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) != 0) {
                    of.set(displayFrames.mRestricted);
                    df.set(displayFrames.mRestricted);
@@ -5185,9 +5213,27 @@ public class PhoneWindowManager implements WindowManagerPolicy {
                        vf.set(cf);
                    }
                }
                pf.intersectUnchecked(displayFrames.mDisplayCutoutSafe);
            }
        }

        // Ensure that windows that did not request to be laid out in the cutout don't get laid
        // out there.
        if (!layoutInCutout) {
            final Rect displayCutoutSafeExceptMaybeTop = mTmpRect;
            displayCutoutSafeExceptMaybeTop.set(displayFrames.mDisplayCutoutSafe);
            if (layoutInScreen && layoutInsetDecor) {
                // At the top we have the status bar, so apps that are
                // LAYOUT_IN_SCREEN | LAYOUT_INSET_DECOR already expect that there's an inset
                // there and we don't need to exclude the window from that area.
                displayCutoutSafeExceptMaybeTop.top = Integer.MIN_VALUE;
            }
            pf.intersectUnchecked(displayCutoutSafeExceptMaybeTop);
        }

        // Content should never appear in the cutout.
        cf.intersectUnchecked(displayFrames.mDisplayCutoutSafe);

        // TYPE_SYSTEM_ERROR is above the NavigationBar so it can't be allowed to extend over it.
        // Also, we don't allow windows in multi-window mode to extend out of the screen.
        if ((fl & FLAG_LAYOUT_NO_LIMITS) != 0 && type != TYPE_SYSTEM_ERROR
+125 −0
Original line number Diff line number Diff line
@@ -16,7 +16,12 @@

package com.android.server.policy;

import static android.view.Surface.ROTATION_180;
import static android.view.Surface.ROTATION_270;
import static android.view.Surface.ROTATION_90;
import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.WindowManager.LayoutParams.FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA;
import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
@@ -29,6 +34,7 @@ import android.graphics.PixelFormat;
import android.platform.test.annotations.Presubmit;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
import android.view.Surface;
import android.view.WindowManager;

import org.junit.Before;
@@ -105,4 +111,123 @@ public class PhoneWindowManagerLayoutTest extends PhoneWindowManagerTestBase {
        assertEquals(0, mAppWindow.attrs.systemUiVisibility);
        assertEquals(0, mAppWindow.attrs.subtreeSystemUiVisibility);
    }

    @Test
    public void layoutWindowLw_withDisplayCutout() {
        addDisplayCutout();

        mPolicy.addWindow(mAppWindow);

        mPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
        mPolicy.layoutWindowLw(mAppWindow, null, mFrames);

        assertInsetByTopBottom(mAppWindow.parentFrame, 0, 0);
        assertInsetByTopBottom(mAppWindow.stableFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
        assertInsetByTopBottom(mAppWindow.contentFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
        assertInsetByTopBottom(mAppWindow.decorFrame, 0, 0);
    }

    @Test
    public void layoutWindowLw_withDisplayCutout_fullscreen() {
        addDisplayCutout();

        mAppWindow.attrs.subtreeSystemUiVisibility |= SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
        mPolicy.addWindow(mAppWindow);

        mPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
        mPolicy.layoutWindowLw(mAppWindow, null, mFrames);

        assertInsetByTopBottom(mAppWindow.parentFrame, STATUS_BAR_HEIGHT, 0);
        assertInsetByTopBottom(mAppWindow.stableFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
        assertInsetByTopBottom(mAppWindow.contentFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
        assertInsetByTopBottom(mAppWindow.decorFrame, 0, 0);
    }

    @Test
    public void layoutWindowLw_withDisplayCutout_fullscreenInCutout() {
        addDisplayCutout();

        mAppWindow.attrs.subtreeSystemUiVisibility |= SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
        mAppWindow.attrs.flags2 |= FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA;
        mPolicy.addWindow(mAppWindow);

        mPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
        mPolicy.layoutWindowLw(mAppWindow, null, mFrames);

        assertInsetByTopBottom(mAppWindow.parentFrame, 0, 0);
        assertInsetByTopBottom(mAppWindow.stableFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
        assertInsetByTopBottom(mAppWindow.contentFrame, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT);
        assertInsetByTopBottom(mAppWindow.decorFrame, 0, 0);
    }


    @Test
    public void layoutWindowLw_withDisplayCutout_landscape() {
        addDisplayCutout();
        setRotation(ROTATION_90);
        mPolicy.addWindow(mAppWindow);

        mPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
        mPolicy.layoutWindowLw(mAppWindow, null, mFrames);

        assertInsetBy(mAppWindow.parentFrame, DISPLAY_CUTOUT_HEIGHT, 0, 0, 0);
        assertInsetBy(mAppWindow.stableFrame, 0, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT, 0);
        assertInsetBy(mAppWindow.contentFrame,
                DISPLAY_CUTOUT_HEIGHT, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT, 0);
        assertInsetBy(mAppWindow.decorFrame, 0, 0, 0, 0);
    }

    @Test
    public void layoutWindowLw_withDisplayCutout_seascape() {
        addDisplayCutout();
        setRotation(ROTATION_270);
        mPolicy.addWindow(mAppWindow);

        mPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
        mPolicy.layoutWindowLw(mAppWindow, null, mFrames);

        assertInsetBy(mAppWindow.parentFrame, 0, 0, DISPLAY_CUTOUT_HEIGHT, 0);
        assertInsetBy(mAppWindow.stableFrame, NAV_BAR_HEIGHT, STATUS_BAR_HEIGHT, 0, 0);
        assertInsetBy(mAppWindow.contentFrame,
                NAV_BAR_HEIGHT, STATUS_BAR_HEIGHT, DISPLAY_CUTOUT_HEIGHT, 0);
        assertInsetBy(mAppWindow.decorFrame, 0, 0, 0, 0);
    }

    @Test
    public void layoutWindowLw_withDisplayCutout_fullscreen_landscape() {
        addDisplayCutout();
        setRotation(ROTATION_90);

        mAppWindow.attrs.subtreeSystemUiVisibility |= SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
        mPolicy.addWindow(mAppWindow);

        mPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
        mPolicy.layoutWindowLw(mAppWindow, null, mFrames);

        assertInsetBy(mAppWindow.parentFrame, DISPLAY_CUTOUT_HEIGHT, 0, 0, 0);
        assertInsetBy(mAppWindow.stableFrame, 0, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT, 0);
        assertInsetBy(mAppWindow.contentFrame,
                DISPLAY_CUTOUT_HEIGHT, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT, 0);
        assertInsetBy(mAppWindow.decorFrame, 0, 0, 0, 0);
    }

    @Test
    public void layoutWindowLw_withDisplayCutout_fullscreenInCutout_landscape() {
        addDisplayCutout();
        setRotation(ROTATION_90);

        mAppWindow.attrs.subtreeSystemUiVisibility |= SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
        mAppWindow.attrs.flags2 |= FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA;
        mPolicy.addWindow(mAppWindow);

        mPolicy.beginLayoutLw(mFrames, 0 /* UI mode */);
        mPolicy.layoutWindowLw(mAppWindow, null, mFrames);

        assertInsetBy(mAppWindow.parentFrame, 0, 0, 0, 0);
        assertInsetBy(mAppWindow.stableFrame, 0, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT, 0);
        assertInsetBy(mAppWindow.contentFrame,
                DISPLAY_CUTOUT_HEIGHT, STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT, 0);
        assertInsetBy(mAppWindow.decorFrame, 0, 0, 0, 0);
    }

}
 No newline at end of file
+26 −5
Original line number Diff line number Diff line
@@ -17,6 +17,9 @@
package com.android.server.policy;

import static android.view.Surface.ROTATION_0;
import static android.view.Surface.ROTATION_180;
import static android.view.Surface.ROTATION_270;
import static android.view.Surface.ROTATION_90;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
@@ -54,6 +57,7 @@ public class PhoneWindowManagerTestBase {

    static final int STATUS_BAR_HEIGHT = 10;
    static final int NAV_BAR_HEIGHT = 15;
    static final int DISPLAY_CUTOUT_HEIGHT = 8;

    TestablePhoneWindowManager mPolicy;
    TestContextWrapper mContext;
@@ -76,10 +80,17 @@ public class PhoneWindowManagerTestBase {

        mPolicy = TestablePhoneWindowManager.create(mContext);

        setRotation(ROTATION_0);
    }

    public void setRotation(int rotation) {
        DisplayInfo info = new DisplayInfo();
        info.logicalWidth = DISPLAY_WIDTH;
        info.logicalHeight = DISPLAY_HEIGHT;
        info.rotation = ROTATION_0;

        final boolean flippedDimensions = rotation == ROTATION_90 || rotation == ROTATION_270;
        info.logicalWidth = flippedDimensions ? DISPLAY_HEIGHT : DISPLAY_WIDTH;
        info.logicalHeight = flippedDimensions ? DISPLAY_WIDTH : DISPLAY_HEIGHT;
        info.rotation = rotation;

        mFrames = new DisplayFrames(Display.DEFAULT_DISPLAY, info);
    }

@@ -95,7 +106,7 @@ public class PhoneWindowManagerTestBase {

    public void addNavigationBar() {
        mNavigationBar = new FakeWindowState();
        mNavigationBar.attrs = new WindowManager.LayoutParams(MATCH_PARENT, STATUS_BAR_HEIGHT,
        mNavigationBar.attrs = new WindowManager.LayoutParams(MATCH_PARENT, NAV_BAR_HEIGHT,
                TYPE_NAVIGATION_BAR, 0 /* flags */, PixelFormat.TRANSLUCENT);
        mNavigationBar.attrs.gravity = Gravity.BOTTOM;

@@ -104,11 +115,16 @@ public class PhoneWindowManagerTestBase {
        mPolicy.mLastSystemUiFlags |= View.NAVIGATION_BAR_TRANSPARENT;
    }

    public void addDisplayCutout() {
        mPolicy.mEmulateDisplayCutout = true;
    }

    /** Asserts that {@code actual} is inset by the given amounts from the full display rect. */
    public void assertInsetBy(Rect actual, int expectedInsetLeft, int expectedInsetTop,
            int expectedInsetRight, int expectedInsetBottom) {
        assertEquals(new Rect(expectedInsetLeft, expectedInsetTop,
                DISPLAY_WIDTH - expectedInsetRight, DISPLAY_HEIGHT - expectedInsetBottom), actual);
                mFrames.mDisplayWidth - expectedInsetRight,
                mFrames.mDisplayHeight - expectedInsetBottom), actual);
    }

    /**
@@ -181,6 +197,11 @@ public class PhoneWindowManagerTestBase {
                policy[0].mAccessibilityManager = new AccessibilityManager(context,
                        mock(IAccessibilityManager.class), UserHandle.USER_CURRENT);
                policy[0].mSystemGestures = mock(SystemGesturesPointerEventListener.class);
                policy[0].mNavigationBarCanMove = true;
                policy[0].mPortraitRotation = ROTATION_0;
                policy[0].mLandscapeRotation = ROTATION_90;
                policy[0].mUpsideDownRotation = ROTATION_180;
                policy[0].mSeascapeRotation = ROTATION_270;
                policy[0].onConfigurationChanged();
            });
            return policy[0];