From e16645a966a740409de49aa94ce84370936e0579 Mon Sep 17 00:00:00 2001 From: Tiger Huang Date: Tue, 25 Feb 2020 22:24:39 +0800 Subject: [PATCH 1/2] Make display cutout can produce insets This CL creates InsetsType and InternalInsetsType for display cutout. With this CL, WindowInsets.getSystemWindowInsets() can be compatible with the legacy insets mode. Fix: 149932355 Test: Open an app which has LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS and hides navigation bar on the display which has double display cutout. And check if the app can get the correct result from WindowInsets.getSystemWindowInsets(). Change-Id: I381a083d8c30e1678c835eaf5341e941139aa0d7 --- core/java/android/view/InsetsSource.java | 4 +++ core/java/android/view/InsetsState.java | 35 +++++++++++++++++-- core/java/android/view/WindowInsets.java | 30 +++++++++++++--- .../com/android/server/wm/DisplayPolicy.java | 24 +++++++++++++ .../server/wm/DisplayPolicyLayoutTests.java | 20 +++++++++++ 5 files changed, 106 insertions(+), 7 deletions(-) diff --git a/core/java/android/view/InsetsSource.java b/core/java/android/view/InsetsSource.java index 6caa4fed6409e..719f649cb5a2f 100644 --- a/core/java/android/view/InsetsSource.java +++ b/core/java/android/view/InsetsSource.java @@ -58,6 +58,10 @@ public class InsetsSource implements Parcelable { : null; } + public void setFrame(int left, int top, int right, int bottom) { + mFrame.set(left, top, right, bottom); + } + public void setFrame(Rect frame) { mFrame.set(frame); } diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java index 8648682aaa2eb..c877c454be91f 100644 --- a/core/java/android/view/InsetsState.java +++ b/core/java/android/view/InsetsState.java @@ -24,6 +24,7 @@ import static android.view.ViewRootImpl.sNewInsetsMode; import static android.view.WindowInsets.Type.MANDATORY_SYSTEM_GESTURES; import static android.view.WindowInsets.Type.SIZE; import static android.view.WindowInsets.Type.SYSTEM_GESTURES; +import static android.view.WindowInsets.Type.displayCutout; import static android.view.WindowInsets.Type.ime; import static android.view.WindowInsets.Type.indexOf; import static android.view.WindowInsets.Type.isVisibleInsetsType; @@ -71,6 +72,10 @@ public class InsetsState implements Parcelable { ITYPE_RIGHT_GESTURES, ITYPE_TOP_TAPPABLE_ELEMENT, ITYPE_BOTTOM_TAPPABLE_ELEMENT, + ITYPE_LEFT_DISPLAY_CUTOUT, + ITYPE_TOP_DISPLAY_CUTOUT, + ITYPE_RIGHT_DISPLAY_CUTOUT, + ITYPE_BOTTOM_DISPLAY_CUTOUT, ITYPE_IME }) public @interface InternalInsetsType {} @@ -88,8 +93,13 @@ public class InsetsState implements Parcelable { public static final int ITYPE_TOP_TAPPABLE_ELEMENT = 7; public static final int ITYPE_BOTTOM_TAPPABLE_ELEMENT = 8; + public static final int ITYPE_LEFT_DISPLAY_CUTOUT = 9; + public static final int ITYPE_TOP_DISPLAY_CUTOUT = 10; + public static final int ITYPE_RIGHT_DISPLAY_CUTOUT = 11; + public static final int ITYPE_BOTTOM_DISPLAY_CUTOUT = 12; + /** Input method window. */ - public static final int ITYPE_IME = 9; + public static final int ITYPE_IME = 13; static final int LAST_TYPE = ITYPE_IME; @@ -185,8 +195,8 @@ public class InsetsState implements Parcelable { final int softInputAdjustMode = legacySoftInputMode & SOFT_INPUT_MASK_ADJUST; return new WindowInsets(typeInsetsMap, typeMaxInsetsMap, typeVisibilityMap, isScreenRound, alwaysConsumeSystemBars, cutout, softInputAdjustMode == SOFT_INPUT_ADJUST_RESIZE - ? systemBars() | ime() - : systemBars(), + ? systemBars() | displayCutout() | ime() + : systemBars() | displayCutout(), sNewInsetsMode == NEW_INSETS_MODE_FULL && (legacySystemUiFlags & SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0); } @@ -358,6 +368,12 @@ public class InsetsState implements Parcelable { if ((types & Type.CAPTION_BAR) != 0) { result.add(ITYPE_CAPTION_BAR); } + if ((types & Type.DISPLAY_CUTOUT) != 0) { + result.add(ITYPE_LEFT_DISPLAY_CUTOUT); + result.add(ITYPE_TOP_DISPLAY_CUTOUT); + result.add(ITYPE_RIGHT_DISPLAY_CUTOUT); + result.add(ITYPE_BOTTOM_DISPLAY_CUTOUT); + } if ((types & Type.IME) != 0) { result.add(ITYPE_IME); } @@ -388,6 +404,11 @@ public class InsetsState implements Parcelable { case ITYPE_TOP_TAPPABLE_ELEMENT: case ITYPE_BOTTOM_TAPPABLE_ELEMENT: return Type.TAPPABLE_ELEMENT; + case ITYPE_LEFT_DISPLAY_CUTOUT: + case ITYPE_TOP_DISPLAY_CUTOUT: + case ITYPE_RIGHT_DISPLAY_CUTOUT: + case ITYPE_BOTTOM_DISPLAY_CUTOUT: + return Type.DISPLAY_CUTOUT; default: throw new IllegalArgumentException("Unknown type: " + type); } @@ -437,6 +458,14 @@ public class InsetsState implements Parcelable { return "ITYPE_TOP_TAPPABLE_ELEMENT"; case ITYPE_BOTTOM_TAPPABLE_ELEMENT: return "ITYPE_BOTTOM_TAPPABLE_ELEMENT"; + case ITYPE_LEFT_DISPLAY_CUTOUT: + return "ITYPE_LEFT_DISPLAY_CUTOUT"; + case ITYPE_TOP_DISPLAY_CUTOUT: + return "ITYPE_TOP_DISPLAY_CUTOUT"; + case ITYPE_RIGHT_DISPLAY_CUTOUT: + return "ITYPE_RIGHT_DISPLAY_CUTOUT"; + case ITYPE_BOTTOM_DISPLAY_CUTOUT: + return "ITYPE_BOTTOM_DISPLAY_CUTOUT"; case ITYPE_IME: return "ITYPE_IME"; default: diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java index a6c311e1daa5a..098e8c2da8b81 100644 --- a/core/java/android/view/WindowInsets.java +++ b/core/java/android/view/WindowInsets.java @@ -17,6 +17,7 @@ package android.view; +import static android.view.WindowInsets.Type.DISPLAY_CUTOUT; import static android.view.WindowInsets.Type.FIRST; import static android.view.WindowInsets.Type.IME; import static android.view.WindowInsets.Type.LAST; @@ -1209,6 +1210,13 @@ public final class WindowInsets { @NonNull public Builder setDisplayCutout(@Nullable DisplayCutout displayCutout) { mDisplayCutout = displayCutout != null ? displayCutout : DisplayCutout.NO_CUTOUT; + if (!mDisplayCutout.isEmpty()) { + final Insets safeInsets = Insets.of(mDisplayCutout.getSafeInsets()); + final int index = indexOf(DISPLAY_CUTOUT); + mTypeInsetsMap[index] = safeInsets; + mTypeMaxInsetsMap[index] = safeInsets; + mTypeVisibilityMap[index] = true; + } return this; } @@ -1256,8 +1264,10 @@ public final class WindowInsets { static final int MANDATORY_SYSTEM_GESTURES = 1 << 5; static final int TAPPABLE_ELEMENT = 1 << 6; - static final int LAST = 1 << 7; - static final int SIZE = 8; + static final int DISPLAY_CUTOUT = 1 << 7; + + static final int LAST = 1 << 8; + static final int SIZE = 9; static final int WINDOW_DECOR = LAST; static int indexOf(@InsetsType int type) { @@ -1276,8 +1286,10 @@ public final class WindowInsets { return 5; case TAPPABLE_ELEMENT: return 6; - case WINDOW_DECOR: + case DISPLAY_CUTOUT: return 7; + case WINDOW_DECOR: + return 8; default: throw new IllegalArgumentException("type needs to be >= FIRST and <= LAST," + " type=" + type); @@ -1290,7 +1302,7 @@ public final class WindowInsets { /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef(flag = true, value = {STATUS_BARS, NAVIGATION_BARS, CAPTION_BAR, IME, WINDOW_DECOR, - SYSTEM_GESTURES, MANDATORY_SYSTEM_GESTURES, TAPPABLE_ELEMENT}) + SYSTEM_GESTURES, MANDATORY_SYSTEM_GESTURES, TAPPABLE_ELEMENT, DISPLAY_CUTOUT}) public @interface InsetsType { } @@ -1357,6 +1369,16 @@ public final class WindowInsets { return TAPPABLE_ELEMENT; } + /** + * @return An insets type representing the area that avoids the display cutout. + * + * @see DisplayCutout#getSafeInsetTop + * @hide + */ + public static @InsetsType int displayCutout() { + return DISPLAY_CUTOUT; + } + /** * @return All system bars. Includes {@link #statusBars()}, {@link #captionBar()} as well as * {@link #navigationBars()}, but not {@link #ime()}. diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index 4e872ac9507e2..44507390142c2 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -25,12 +25,16 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECOND import static android.content.res.Configuration.UI_MODE_TYPE_CAR; import static android.content.res.Configuration.UI_MODE_TYPE_MASK; import static android.view.Display.TYPE_INTERNAL; +import static android.view.InsetsState.ITYPE_BOTTOM_DISPLAY_CUTOUT; import static android.view.InsetsState.ITYPE_BOTTOM_GESTURES; import static android.view.InsetsState.ITYPE_BOTTOM_TAPPABLE_ELEMENT; +import static android.view.InsetsState.ITYPE_LEFT_DISPLAY_CUTOUT; import static android.view.InsetsState.ITYPE_LEFT_GESTURES; import static android.view.InsetsState.ITYPE_NAVIGATION_BAR; +import static android.view.InsetsState.ITYPE_RIGHT_DISPLAY_CUTOUT; import static android.view.InsetsState.ITYPE_RIGHT_GESTURES; import static android.view.InsetsState.ITYPE_STATUS_BAR; +import static android.view.InsetsState.ITYPE_TOP_DISPLAY_CUTOUT; import static android.view.InsetsState.ITYPE_TOP_GESTURES; import static android.view.InsetsState.ITYPE_TOP_TAPPABLE_ELEMENT; import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN; @@ -1430,6 +1434,7 @@ public class DisplayPolicy { */ void simulateLayoutDisplay(DisplayFrames displayFrames, InsetsState insetsState, int uiMode) { displayFrames.onBeginLayout(); + updateInsetsStateForDisplayCutout(displayFrames, insetsState); insetsState.setDisplayFrame(displayFrames.mUnrestricted); final WindowFrames simulatedWindowFrames = new WindowFrames(); if (mNavigationBar != null) { @@ -1458,6 +1463,8 @@ public class DisplayPolicy { */ public void beginLayoutLw(DisplayFrames displayFrames, int uiMode) { displayFrames.onBeginLayout(); + updateInsetsStateForDisplayCutout(displayFrames, + mDisplayContent.getInsetsStateController().getRawInsetsState()); mSystemGestures.screenWidth = displayFrames.mUnrestricted.width(); mSystemGestures.screenHeight = displayFrames.mUnrestricted.height(); @@ -1524,6 +1531,23 @@ public class DisplayPolicy { mLastNotificationShadeForcesShowingNavigation = notificationShadeForcesShowingNavigation; } + private static void updateInsetsStateForDisplayCutout(DisplayFrames displayFrames, + InsetsState state) { + if (displayFrames.mDisplayCutout.getDisplayCutout().isEmpty()) { + state.removeSource(ITYPE_LEFT_DISPLAY_CUTOUT); + state.removeSource(ITYPE_TOP_DISPLAY_CUTOUT); + state.removeSource(ITYPE_RIGHT_DISPLAY_CUTOUT); + state.removeSource(ITYPE_BOTTOM_DISPLAY_CUTOUT); + return; + } + final Rect u = displayFrames.mUnrestricted; + final Rect s = displayFrames.mDisplayCutoutSafe; + state.getSource(ITYPE_LEFT_DISPLAY_CUTOUT).setFrame(u.left, u.top, s.left, u.bottom); + state.getSource(ITYPE_TOP_DISPLAY_CUTOUT).setFrame(u.left, u.top, u.right, s.top); + state.getSource(ITYPE_RIGHT_DISPLAY_CUTOUT).setFrame(s.right, u.top, u.right, u.bottom); + state.getSource(ITYPE_BOTTOM_DISPLAY_CUTOUT).setFrame(u.left, s.bottom, u.right, u.bottom); + } + /** Enforces the last layout policy for display frames. */ private void postAdjustDisplayFrames(DisplayFrames displayFrames) { if (displayFrames.mDisplayCutoutSafe.top > displayFrames.mUnrestricted.top) { diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java index 203fa61b75464..e2c27ea55b13a 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java @@ -269,6 +269,26 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { assertInsetByTopBottom(mWindow.getDecorFrame(), 0, 0); } + @Test + public void layoutWindowLw_fitDisplayCutout() { + assumeTrue(ViewRootImpl.sNewInsetsMode == ViewRootImpl.NEW_INSETS_MODE_FULL); + addDisplayCutout(); + + mWindow.mAttrs.setFitInsetsTypes(Type.displayCutout()); + mWindow.mAttrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; + addWindow(mWindow); + + mDisplayPolicy.beginLayoutLw(mFrames, 0 /* UI mode */); + mDisplayPolicy.layoutWindowLw(mWindow, null, mFrames); + + assertInsetByTopBottom(mWindow.getStableFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); + assertInsetByTopBottom(mWindow.getDisplayFrameLw(), DISPLAY_CUTOUT_HEIGHT, 0); + assertInsetByTopBottom(mWindow.getParentFrame(), DISPLAY_CUTOUT_HEIGHT, 0); + assertInsetByTopBottom(mWindow.getVisibleFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); + assertInsetByTopBottom(mWindow.getContentFrameLw(), STATUS_BAR_HEIGHT, NAV_BAR_HEIGHT); + assertInsetByTopBottom(mWindow.getDecorFrame(), 0, 0); + } + // TODO(b/118118435): remove after migration @Test public void layoutWindowLw_appDrawsBarsLegacy() { From e06d0714464daf803d9a2e0ac915edc49f41c718 Mon Sep 17 00:00:00 2001 From: Tiger Huang Date: Wed, 26 Feb 2020 20:36:13 +0800 Subject: [PATCH 2/2] Unhide API: WindowInsets.Type#displayCutout() With this CL, the public developers can interact with display cutout via insets APIs. Bug: 118118435 Test: build Change-Id: If5ccacddb079bc5513ccb5820ea1c371e95fe562 --- api/current.txt | 1 + core/java/android/view/DisplayCutout.java | 24 +++++++++++++++++++---- core/java/android/view/WindowInsets.java | 10 +++++++--- 3 files changed, 28 insertions(+), 7 deletions(-) diff --git a/api/current.txt b/api/current.txt index 8477754d9095e..e2d054800d4a5 100644 --- a/api/current.txt +++ b/api/current.txt @@ -55520,6 +55520,7 @@ package android.view { public static final class WindowInsets.Type { method public static int captionBar(); + method public static int displayCutout(); method public static int ime(); method public static int mandatorySystemGestures(); method public static int navigationBars(); diff --git a/core/java/android/view/DisplayCutout.java b/core/java/android/view/DisplayCutout.java index 0ab856ec5b4d1..679c912e846f2 100644 --- a/core/java/android/view/DisplayCutout.java +++ b/core/java/android/view/DisplayCutout.java @@ -393,22 +393,38 @@ public final class DisplayCutout { return mSafeInsets.equals(ZERO_RECT); } - /** Returns the inset from the top which avoids the display cutout in pixels. */ + /** + * Returns the inset from the top which avoids the display cutout in pixels. + * + * @see WindowInsets.Type#displayCutout() + */ public int getSafeInsetTop() { return mSafeInsets.top; } - /** Returns the inset from the bottom which avoids the display cutout in pixels. */ + /** + * Returns the inset from the bottom which avoids the display cutout in pixels. + * + * @see WindowInsets.Type#displayCutout() + */ public int getSafeInsetBottom() { return mSafeInsets.bottom; } - /** Returns the inset from the left which avoids the display cutout in pixels. */ + /** + * Returns the inset from the left which avoids the display cutout in pixels. + * + * @see WindowInsets.Type#displayCutout() + */ public int getSafeInsetLeft() { return mSafeInsets.left; } - /** Returns the inset from the right which avoids the display cutout in pixels. */ + /** + * Returns the inset from the right which avoids the display cutout in pixels. + * + * @see WindowInsets.Type#displayCutout() + */ public int getSafeInsetRight() { return mSafeInsets.right; } diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java index 098e8c2da8b81..20fa5cab04647 100644 --- a/core/java/android/view/WindowInsets.java +++ b/core/java/android/view/WindowInsets.java @@ -1370,10 +1370,14 @@ public final class WindowInsets { } /** - * @return An insets type representing the area that avoids the display cutout. + * Returns an insets type representing the area that used by {@link DisplayCutout}. * - * @see DisplayCutout#getSafeInsetTop - * @hide + *

This is equivalent to the safe insets on {@link #getDisplayCutout()}.

+ * + * @see DisplayCutout#getSafeInsetLeft() + * @see DisplayCutout#getSafeInsetTop() + * @see DisplayCutout#getSafeInsetRight() + * @see DisplayCutout#getSafeInsetBottom() */ public static @InsetsType int displayCutout() { return DISPLAY_CUTOUT;