From a5dffa77622ca3c4e8a99974aa97fc96d7ceea01 Mon Sep 17 00:00:00 2001 From: Yunfan Chen Date: Wed, 10 Nov 2021 16:09:49 +0900 Subject: [PATCH] Introduce a private flag to extend frame with cutout This patch introduced a new private flag PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_TO_CUTOUT to let the window extend it's requested window frame to contain the display cutout frame. This is useful for the case of navigation bar. Without it, the navigation bar may not have enough space to layout it's content when it's on the same side as the display cutout. With the flag set, the Navigation bar will have the requested size plus the navigation bar size as its frame. Bug: 203031262 Bug: 161689946 Test: See reproduce steps in b/203031262 Test: atest WindowLayoutTests Change-Id: Id1d6f09dc421b5f25e16e4aca6282b6deec2e8c4 --- core/java/android/view/ViewRootImpl.java | 59 ++++++++++--------- core/java/android/view/WindowLayout.java | 31 ++++++++++ core/java/android/view/WindowManager.java | 10 ++++ .../systemui/navigationbar/NavigationBar.java | 6 +- .../android/server/wm/WindowLayoutTests.java | 18 ++++++ 5 files changed, 94 insertions(+), 30 deletions(-) diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index feb17f54acd67..03f05f2468ae8 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -76,6 +76,7 @@ import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_BEHAVIOR_CONT import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FIT_INSETS_CONTROLLED; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_INSET_PARENT_FRAME_BY_IME; +import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT; import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG; @@ -2461,8 +2462,9 @@ public final class ViewRootImpl implements ViewParent, if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": baseSize=" + baseSize + ", desiredWindowWidth=" + desiredWindowWidth); if (baseSize != 0 && desiredWindowWidth > baseSize) { - childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width); - childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height); + childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width, lp.privateFlags); + childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height, + lp.privateFlags); performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": measured (" + host.getMeasuredWidth() + "," + host.getMeasuredHeight() @@ -2475,7 +2477,7 @@ public final class ViewRootImpl implements ViewParent, baseSize = (baseSize+desiredWindowWidth)/2; if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": next baseSize=" + baseSize); - childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width); + childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width, lp.privateFlags); performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": measured (" + host.getMeasuredWidth() + "," + host.getMeasuredHeight() + ")"); @@ -2488,8 +2490,10 @@ public final class ViewRootImpl implements ViewParent, } if (!goodMeasure) { - childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width); - childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height); + childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width, + lp.privateFlags); + childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height, + lp.privateFlags); performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); if (mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight()) { windowSizeMayChange = true; @@ -3149,8 +3153,10 @@ public final class ViewRootImpl implements ViewParent, if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight() || dispatchApplyInsets || updatedConfiguration) { - int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width); - int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height); + int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width, + lp.privateFlags); + int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height, + lp.privateFlags); if (DEBUG_LAYOUT) Log.v(mTag, "Ooops, something changed! mWidth=" + mWidth + " measuredWidth=" + host.getMeasuredWidth() @@ -3950,31 +3956,28 @@ public final class ViewRootImpl implements ViewParent, * Figures out the measure spec for the root view in a window based on it's * layout params. * - * @param windowSize - * The available width or height of the window - * - * @param rootDimension - * The layout params for one dimension (width or height) of the - * window. - * + * @param windowSize The available width or height of the window. + * @param measurement The layout width or height requested in the layout params. + * @param privateFlags The private flags in the layout params of the window. * @return The measure spec to use to measure the root view. */ - private static int getRootMeasureSpec(int windowSize, int rootDimension) { + private static int getRootMeasureSpec(int windowSize, int measurement, int privateFlags) { int measureSpec; + final int rootDimension = (privateFlags & PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT) != 0 + ? MATCH_PARENT : measurement; switch (rootDimension) { - - case ViewGroup.LayoutParams.MATCH_PARENT: - // Window can't resize. Force root view to be windowSize. - measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY); - break; - case ViewGroup.LayoutParams.WRAP_CONTENT: - // Window can resize. Set max size for root view. - measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST); - break; - default: - // Window wants to be an exact size. Force root view to be that size. - measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY); - break; + case ViewGroup.LayoutParams.MATCH_PARENT: + // Window can't resize. Force root view to be windowSize. + measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY); + break; + case ViewGroup.LayoutParams.WRAP_CONTENT: + // Window can resize. Set max size for root view. + measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST); + break; + default: + // Window wants to be an exact size. Force root view to be that size. + measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY); + break; } return measureSpec; } diff --git a/core/java/android/view/WindowLayout.java b/core/java/android/view/WindowLayout.java index 27f89ec4ac79e..ad9f21b4fa70d 100644 --- a/core/java/android/view/WindowLayout.java +++ b/core/java/android/view/WindowLayout.java @@ -16,6 +16,8 @@ package android.view; +import static android.view.Gravity.DISPLAY_CLIP_HORIZONTAL; +import static android.view.Gravity.DISPLAY_CLIP_VERTICAL; import static android.view.InsetsState.ITYPE_IME; import static android.view.InsetsState.ITYPE_NAVIGATION_BAR; import static android.view.InsetsState.ITYPE_STATUS_BAR; @@ -29,6 +31,7 @@ import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_M import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_INSET_PARENT_FRAME_BY_IME; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME; +import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT; import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD; import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ERROR; @@ -250,11 +253,39 @@ public class WindowLayout { Gravity.apply(attrs.gravity, w, h, outParentFrame, (int) (x + attrs.horizontalMargin * pw), (int) (y + attrs.verticalMargin * ph), outFrame); + // Now make sure the window fits in the overall display frame. if (fitToDisplay) { Gravity.applyDisplay(attrs.gravity, outDisplayFrame, outFrame); } + if ((attrs.privateFlags & PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT) != 0 + && !cutout.isEmpty()) { + // If the actual frame covering a display cutout, and the window is requesting to extend + // it's requested frame, re-do the frame calculation after getting the new requested + // size. + mTempRect.set(outFrame); + // Do nothing if the display cutout and window don't overlap entirely. This may happen + // when the cutout is not on the same side with the window. + boolean shouldExpand = false; + final Rect [] cutoutBounds = cutout.getBoundingRectsAll(); + for (Rect cutoutBound : cutoutBounds) { + if (cutoutBound.isEmpty()) continue; + if (mTempRect.contains(cutoutBound) || cutoutBound.contains(mTempRect)) { + shouldExpand = true; + break; + } + } + if (shouldExpand) { + // Try to fit move the bar to avoid the display cutout first. Make sure the clip + // flags are not set to make the window move. + final int clipFlags = DISPLAY_CLIP_VERTICAL | DISPLAY_CLIP_HORIZONTAL; + Gravity.applyDisplay(attrs.gravity & ~clipFlags, displayCutoutSafe, + mTempRect); + outFrame.union(mTempRect); + } + } + if (DEBUG) Log.d(TAG, "computeWindowFrames " + attrs.getTitle() + " outFrame=" + outFrame.toShortString() + " outParentFrame=" + outParentFrame.toShortString() diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index 8ee3e432cbbb9..dbfae46a6e724 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -2384,6 +2384,16 @@ public interface WindowManager extends ViewManager { */ public static final int PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR = 0x00001000; + /** + * Flag to indicate that the window frame should be the requested frame adding the display + * cutout frame. This will only be applied if a specific size smaller than the parent frame + * is given, and the window is covering the display cutout. The extended frame will not be + * larger than the parent frame. + * + * {@hide} + */ + public static final int PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT = 0x00002000; + /** * Flag that will make window ignore app visibility and instead depend purely on the decor * view visibility for determining window visibility. This is used by recents to keep diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java index 76914f3fd8ee0..3370db1fb8771 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java @@ -757,7 +757,8 @@ public class NavigationBar implements View.OnAttachStateChangeListener, | WindowManager.LayoutParams.FLAG_SLIPPERY, PixelFormat.TRANSLUCENT); mOrientationParams.setTitle("SecondaryHomeHandle" + mContext.getDisplayId()); - mOrientationParams.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION; + mOrientationParams.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION + | WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT; mWindowManager.addView(mOrientationHandle, mOrientationParams); mOrientationHandle.setVisibility(View.GONE); mOrientationParams.setFitInsetsTypes(0 /* types*/); @@ -1563,7 +1564,8 @@ public class NavigationBar implements View.OnAttachStateChangeListener, } lp.token = new Binder(); lp.accessibilityTitle = mContext.getString(R.string.nav_bar); - lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC; + lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC + | WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT; lp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; lp.windowAnimations = 0; lp.setTitle("NavigationBar" + mContext.getDisplayId()); diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowLayoutTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowLayoutTests.java index 338555e4c9fc6..7d2e9bf26b01c 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowLayoutTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowLayoutTests.java @@ -30,6 +30,7 @@ import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_M import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER; import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_INSET_PARENT_FRAME_BY_IME; +import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT; import static org.junit.Assert.assertEquals; @@ -329,6 +330,23 @@ public class WindowLayoutTests { assertInsetBy(WATERFALL_INSETS.left, 0, WATERFALL_INSETS.right, 0, mFrame); } + @Test + public void layoutExtendedToDisplayCutout() { + addDisplayCutout(); + final int height = DISPLAY_HEIGHT / 2; + mRequestedHeight = UNSPECIFIED_LENGTH; + mAttrs.height = height; + mAttrs.gravity = Gravity.TOP; + mAttrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; + mAttrs.setFitInsetsTypes(0); + mAttrs.privateFlags |= PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT; + computeFrames(); + + assertRect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT, mDisplayFrame); + assertRect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT, mParentFrame); + assertRect(0, 0, DISPLAY_WIDTH, height + DISPLAY_CUTOUT_HEIGHT, mFrame); + } + @Test public void layoutInDisplayCutoutModeDefaultWithInvisibleSystemBars() { addDisplayCutout();