From 7d409b07ed3f603f45e9c1d290f7b15382490db7 Mon Sep 17 00:00:00 2001 From: Tiger <tigerhuang@google.com> Date: Wed, 29 May 2024 21:37:48 +0800 Subject: [PATCH] Let action bar background extend into system insets This CL makes action bar background extend into system insets if the content view doesn't fit any insets. Bug: 343131420 Bug: 309578419 Flag: com.android.window.flags.enforce_edge_to_edge Test: atest ActionBarOverlayLayoutTest Change-Id: I266e55fce8e5a387ae0b56f7c22756d9ec153611 --- .../internal/widget/ActionBarContainer.java | 18 ++- .../widget/ActionBarOverlayLayout.java | 124 ++++++++++++++---- .../widget/ActionBarOverlayLayoutTest.java | 19 ++- 3 files changed, 120 insertions(+), 41 deletions(-) diff --git a/core/java/com/android/internal/widget/ActionBarContainer.java b/core/java/com/android/internal/widget/ActionBarContainer.java index eef33684e883..606e038a1a4b 100644 --- a/core/java/com/android/internal/widget/ActionBarContainer.java +++ b/core/java/com/android/internal/widget/ActionBarContainer.java @@ -93,8 +93,7 @@ public class ActionBarContainer extends FrameLayout { if (bg != null) { bg.setCallback(this); if (mActionBarView != null) { - mBackground.setBounds(mActionBarView.getLeft(), mActionBarView.getTop(), - mActionBarView.getRight(), mActionBarView.getBottom()); + bg.setBounds(0, 0, getMeasuredWidth(), getMeasuredHeight()); } } setWillNotDraw(mIsSplit ? mSplitBackground == null : @@ -293,6 +292,7 @@ public class ActionBarContainer extends FrameLayout { if (mActionBarView == null) return; if (mTabContainer != null && mTabContainer.getVisibility() != GONE) { + final int verticalPadding = getPaddingTop() + getPaddingBottom(); int nonTabMaxHeight = 0; final int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { @@ -307,7 +307,9 @@ public class ActionBarContainer extends FrameLayout { final int maxHeight = mode == MeasureSpec.AT_MOST ? MeasureSpec.getSize(heightMeasureSpec) : Integer.MAX_VALUE; setMeasuredDimension(getMeasuredWidth(), - Math.min(nonTabMaxHeight + getMeasuredHeightWithMargins(mTabContainer), + Math.min( + verticalPadding + nonTabMaxHeight + + getMeasuredHeightWithMargins(mTabContainer), maxHeight)); } } @@ -335,13 +337,9 @@ public class ActionBarContainer extends FrameLayout { } } else { if (mBackground != null) { - if (mActionBarView.getVisibility() == View.VISIBLE) { - mBackground.setBounds(mActionBarView.getLeft(), mActionBarView.getTop(), - mActionBarView.getRight(), mActionBarView.getBottom()); - } else if (mActionContextView != null && - mActionContextView.getVisibility() == View.VISIBLE) { - mBackground.setBounds(mActionContextView.getLeft(), mActionContextView.getTop(), - mActionContextView.getRight(), mActionContextView.getBottom()); + if ((mActionBarView.getVisibility() == View.VISIBLE) || (mActionContextView != null + && mActionContextView.getVisibility() == View.VISIBLE)) { + mBackground.setBounds(0, 0, getMeasuredWidth(), getMeasuredHeight()); } else { mBackground.setBounds(0, 0, 0, 0); } diff --git a/core/java/com/android/internal/widget/ActionBarOverlayLayout.java b/core/java/com/android/internal/widget/ActionBarOverlayLayout.java index 0992db91356d..707f1094a8bc 100644 --- a/core/java/com/android/internal/widget/ActionBarOverlayLayout.java +++ b/core/java/com/android/internal/widget/ActionBarOverlayLayout.java @@ -24,6 +24,7 @@ import android.content.pm.ActivityInfo; import android.content.res.Configuration; import android.content.res.TypedArray; import android.graphics.Canvas; +import android.graphics.Insets; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.os.Build; @@ -77,10 +78,13 @@ public class ActionBarOverlayLayout extends ViewGroup implements DecorContentPar private final Rect mBaseContentInsets = new Rect(); private final Rect mLastBaseContentInsets = new Rect(); private final Rect mContentInsets = new Rect(); + private final Rect mSystemInsets = new Rect(); private WindowInsets mBaseInnerInsets = WindowInsets.CONSUMED; private WindowInsets mLastBaseInnerInsets = WindowInsets.CONSUMED; private WindowInsets mInnerInsets = WindowInsets.CONSUMED; private WindowInsets mLastInnerInsets = WindowInsets.CONSUMED; + private boolean mDecorFitsSystemWindows = true; + private boolean mActionBarExtendsIntoSystemInsets = false; private ActionBarVisibilityCallback mActionBarVisibilityCallback; @@ -268,7 +272,8 @@ public class ActionBarOverlayLayout extends ViewGroup implements DecorContentPar // We want the bar to be visible if it is not being hidden, // or the app has not turned on a stable UI mode (meaning they // are performing explicit layout around the action bar). - mActionBarVisibilityCallback.enableContentAnimations(!stable); + mActionBarVisibilityCallback.enableContentAnimations( + !stable && !mActionBarExtendsIntoSystemInsets); if (barVisible || !stable) mActionBarVisibilityCallback.showForSystem(); else mActionBarVisibilityCallback.hideForSystem(); } @@ -288,25 +293,56 @@ public class ActionBarOverlayLayout extends ViewGroup implements DecorContentPar } } - private boolean applyInsets(View view, Rect insets, boolean left, boolean top, - boolean bottom, boolean right) { + private boolean applyInsets(View view, Rect insets, boolean toPadding, + boolean left, boolean top, boolean bottom, boolean right) { + boolean changed; + if (toPadding) { + changed = setMargin(view, left, top, bottom, right, 0, 0, 0, 0); + changed |= setPadding(view, left, top, bottom, right, + insets.left, insets.top, insets.right, insets.bottom); + } else { + changed = setPadding(view, left, top, bottom, right, 0, 0, 0, 0); + changed |= setMargin(view, left, top, bottom, right, + insets.left, insets.top, insets.right, insets.bottom); + } + return changed; + } + + private boolean setPadding(View view, boolean left, boolean top, boolean bottom, boolean right, + int l, int t, int r, int b) { + if ((left && view.getPaddingLeft() != l) + || (top && view.getPaddingTop() != t) + || (right && view.getPaddingRight() != r) + || (bottom && view.getPaddingBottom() != b)) { + view.setPadding( + left ? l : view.getPaddingLeft(), + top ? t : view.getPaddingTop(), + right ? r : view.getPaddingRight(), + bottom ? b : view.getPaddingBottom()); + return true; + } + return false; + } + + private boolean setMargin(View view, boolean left, boolean top, boolean bottom, boolean right, + int l, int t, int r, int b) { + LayoutParams lp = (LayoutParams) view.getLayoutParams(); boolean changed = false; - LayoutParams lp = (LayoutParams)view.getLayoutParams(); - if (left && lp.leftMargin != insets.left) { + if (left && lp.leftMargin != l) { changed = true; - lp.leftMargin = insets.left; + lp.leftMargin = l; } - if (top && lp.topMargin != insets.top) { + if (top && lp.topMargin != t) { changed = true; - lp.topMargin = insets.top; + lp.topMargin = t; } - if (right && lp.rightMargin != insets.right) { + if (right && lp.rightMargin != r) { changed = true; - lp.rightMargin = insets.right; + lp.rightMargin = r; } - if (bottom && lp.bottomMargin != insets.bottom) { + if (bottom && lp.bottomMargin != b) { changed = true; - lp.bottomMargin = insets.bottom; + lp.bottomMargin = b; } return changed; } @@ -316,12 +352,28 @@ public class ActionBarOverlayLayout extends ViewGroup implements DecorContentPar pullChildren(); final int vis = getWindowSystemUiVisibility(); - final Rect systemInsets = insets.getSystemWindowInsetsAsRect(); + final boolean stable = (vis & SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0; + final boolean layoutIntoSystemInsets = (vis & SYSTEM_UI_LAYOUT_FLAGS) != 0; + mDecorFitsSystemWindows = hasContentOnApplyWindowInsetsListener(); + + // Only extend action bar into system insets area if the app doesn't fit system insets. + mActionBarExtendsIntoSystemInsets = + !mDecorFitsSystemWindows || (stable && layoutIntoSystemInsets); + + if (mActionBarVisibilityCallback != null) { + mActionBarVisibilityCallback.enableContentAnimations( + !stable && !mActionBarExtendsIntoSystemInsets); + } + + final Insets sysInsets = insets.getSystemWindowInsets(); + mSystemInsets.set(sysInsets.left, sysInsets.top, sysInsets.right, sysInsets.bottom); // The top and bottom action bars are always within the content area. - boolean changed = applyInsets(mActionBarTop, systemInsets, true, true, false, true); + boolean changed = applyInsets(mActionBarTop, mSystemInsets, + mActionBarExtendsIntoSystemInsets, true, true, false, true); if (mActionBarBottom != null) { - changed |= applyInsets(mActionBarBottom, systemInsets, true, false, true, true); + changed |= applyInsets(mActionBarBottom, mSystemInsets, + mActionBarExtendsIntoSystemInsets, true, false, true, true); } // Cannot use the result of computeSystemWindowInsets, because that consumes the @@ -406,6 +458,9 @@ public class ActionBarOverlayLayout extends ViewGroup implements DecorContentPar // This is the standard space needed for the action bar. For stable measurement, // we can't depend on the size currently reported by it -- this must remain constant. topInset = mActionBarHeight; + if (mActionBarExtendsIntoSystemInsets) { + topInset += mSystemInsets.top; + } if (mHasNonEmbeddedTabs) { final View tabs = mActionBarTop.getTabContainer(); if (tabs != null) { @@ -424,6 +479,9 @@ public class ActionBarOverlayLayout extends ViewGroup implements DecorContentPar if (mActionBarBottom != null) { if (stable) { bottomInset = mActionBarHeight; + if (mActionBarExtendsIntoSystemInsets) { + bottomInset += mSystemInsets.bottom; + } } else { bottomInset = mActionBarBottom.getMeasuredHeight(); } @@ -436,21 +494,35 @@ public class ActionBarOverlayLayout extends ViewGroup implements DecorContentPar // overlay. mContentInsets.set(mBaseContentInsets); mInnerInsets = mBaseInnerInsets; - if (!mOverlayMode && !stable && hasContentOnApplyWindowInsetsListener()) { - mContentInsets.top += topInset; - mContentInsets.bottom += bottomInset; + if (!mOverlayMode && !stable && mDecorFitsSystemWindows) { + if (mActionBarExtendsIntoSystemInsets) { + mContentInsets.top = Math.max(mContentInsets.top, topInset); + mContentInsets.bottom = Math.max(mContentInsets.bottom, bottomInset); + } else { + mContentInsets.top += topInset; + mContentInsets.bottom += bottomInset; + } // Content view has been shrunk, shrink all insets to match. mInnerInsets = mInnerInsets.inset(0 /* left */, topInset, 0 /* right */, bottomInset); } else { // Add ActionBar to system window inset, but leave other insets untouched. - mInnerInsets = mInnerInsets.replaceSystemWindowInsets( - mInnerInsets.getSystemWindowInsetLeft(), - mInnerInsets.getSystemWindowInsetTop() + topInset, - mInnerInsets.getSystemWindowInsetRight(), - mInnerInsets.getSystemWindowInsetBottom() + bottomInset - ); - } - applyInsets(mContent, mContentInsets, true, true, true, true); + if (mActionBarExtendsIntoSystemInsets) { + mInnerInsets = mInnerInsets.replaceSystemWindowInsets( + mInnerInsets.getSystemWindowInsetLeft(), + Math.max(mInnerInsets.getSystemWindowInsetTop(), topInset), + mInnerInsets.getSystemWindowInsetRight(), + Math.max(mInnerInsets.getSystemWindowInsetBottom(), bottomInset) + ); + } else { + mInnerInsets = mInnerInsets.replaceSystemWindowInsets( + mInnerInsets.getSystemWindowInsetLeft(), + mInnerInsets.getSystemWindowInsetTop() + topInset, + mInnerInsets.getSystemWindowInsetRight(), + mInnerInsets.getSystemWindowInsetBottom() + bottomInset + ); + } + } + applyInsets(mContent, mContentInsets, false /* toPadding */, true, true, true, true); if (!mLastInnerInsets.equals(mInnerInsets)) { // If the inner insets have changed, we need to dispatch this down to diff --git a/core/tests/coretests/src/com/android/internal/widget/ActionBarOverlayLayoutTest.java b/core/tests/coretests/src/com/android/internal/widget/ActionBarOverlayLayoutTest.java index 1dbb7758bf39..2b8adcbe0656 100644 --- a/core/tests/coretests/src/com/android/internal/widget/ActionBarOverlayLayoutTest.java +++ b/core/tests/coretests/src/com/android/internal/widget/ActionBarOverlayLayoutTest.java @@ -20,6 +20,7 @@ import static android.view.DisplayCutout.NO_CUTOUT; import static android.view.View.MeasureSpec.EXACTLY; import static android.view.View.MeasureSpec.makeMeasureSpec; import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; +import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; import static org.hamcrest.CoreMatchers.nullValue; import static org.hamcrest.Matchers.is; @@ -69,6 +70,7 @@ public class ActionBarOverlayLayoutTest { private ViewGroup mContent; private ViewGroup mActionBarTop; + private ViewGroup mActionBarView; private Toolbar mToolbar; private FakeOnApplyWindowListener mContentInsetsListener; @@ -86,15 +88,22 @@ public class ActionBarOverlayLayoutTest { mContentInsetsListener = new FakeOnApplyWindowListener(); mContent.setOnApplyWindowInsetsListener(mContentInsetsListener); + // mActionBarView and mToolbar are supposed to be the same view. Here makes mToolbar a child + // of mActionBarView is to control the height of mActionBarView. In this way, the child + // views of mToolbar won't affect the measurement of mActionBarView or mActionBarTop. + mActionBarView = new FrameLayout(mContext); + mActionBarView.setLayoutParams(new ViewGroup.LayoutParams(MATCH_PARENT, 20)); + + mToolbar = new Toolbar(mContext); + mToolbar.setId(com.android.internal.R.id.action_bar); + mActionBarView.addView(mToolbar); + mActionBarTop = new ActionBarContainer(mContext); mActionBarTop.setId(com.android.internal.R.id.action_bar_container); - mActionBarTop.setLayoutParams(new ViewGroup.LayoutParams(MATCH_PARENT, 20)); + mActionBarTop.setLayoutParams(new ViewGroup.LayoutParams(MATCH_PARENT, WRAP_CONTENT)); + mActionBarTop.addView(mActionBarView); mLayout.addView(mActionBarTop); mLayout.setActionBarHeight(20); - - mToolbar = new Toolbar(mContext); - mToolbar.setId(com.android.internal.R.id.action_bar); - mActionBarTop.addView(mToolbar); } @Test -- GitLab