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