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

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

Display Cutout: Fix ActionBarOverlayLayout to properly dispatch cutout

ActionBarOverlayLayout used to drop WindowInsets, extract the content insets
as a rect, and then dispatch a modified rect to the content view; this because
there was no way to retarget the WindowInsets to the content view, and the
WindowInsets were not truly immutable. That means however, that other kinds of
insets than the content insets do not get dispatched, such as the display cutout.

To fix this, we add APIs to inset WindowInsets, make them immutable. Note that
a similar change is needed for the support lib.

Bug: 79733300
Test: atest ActionBarOverlayLayoutTest
Change-Id: I6a69d8462163ca5e66fdb53f83def6bc4063f8aa
parent 6d084215
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -6175,6 +6175,7 @@ Landroid/view/WindowContentFrameStats;->init(J[J[J[J)V
Landroid/view/WindowInsets;-><init>(Landroid/graphics/Rect;)V
Landroid/view/WindowInsets;->CONSUMED:Landroid/view/WindowInsets;
Landroid/view/WindowInsets;->getSystemWindowInsets()Landroid/graphics/Rect;
Landroid/view/WindowInsets;->inset(IIII)Landroid/view/WindowInsets;
Landroid/view/WindowLeaked;-><init>(Ljava/lang/String;)V
Landroid/view/WindowManager$LayoutParams;->backup()V
Landroid/view/WindowManager$LayoutParams;->FLAG_SLIPPERY:I
+20 −22
Original line number Diff line number Diff line
@@ -9838,26 +9838,20 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
    /**
     * @hide Compute the insets that should be consumed by this view and the ones
     * that should propagate to those under it.
     *
     * Note: This is used by appcompat's ActionBarOverlayLayout through reflection.
     *
     * @param inoutInsets the insets given to this view
     * @param outLocalInsets the insets that should be applied to this view
     * @deprecated use {@link #computeSystemWindowInsets}
     * @return
     */
    @Deprecated
    protected boolean computeFitSystemWindows(Rect inoutInsets, Rect outLocalInsets) {
        if ((mViewFlags & OPTIONAL_FITS_SYSTEM_WINDOWS) == 0
                || mAttachInfo == null
                || ((mAttachInfo.mSystemUiVisibility & SYSTEM_UI_LAYOUT_FLAGS) == 0
                        && !mAttachInfo.mOverscanRequested)) {
            outLocalInsets.set(inoutInsets);
            inoutInsets.set(0, 0, 0, 0);
            return true;
        } else {
            // The application wants to take care of fitting system window for
            // the content...  however we still need to take care of any overscan here.
            final Rect overscan = mAttachInfo.mOverscanInsets;
            outLocalInsets.set(overscan);
            inoutInsets.left -= overscan.left;
            inoutInsets.top -= overscan.top;
            inoutInsets.right -= overscan.right;
            inoutInsets.bottom -= overscan.bottom;
            return false;
        }
        WindowInsets innerInsets = computeSystemWindowInsets(new WindowInsets(inoutInsets),
                outLocalInsets);
        inoutInsets.set(innerInsets.getSystemWindowInsets());
        return innerInsets.isSystemWindowInsetsConsumed();
    }
    /**
@@ -9873,12 +9867,16 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
    public WindowInsets computeSystemWindowInsets(WindowInsets in, Rect outLocalInsets) {
        if ((mViewFlags & OPTIONAL_FITS_SYSTEM_WINDOWS) == 0
                || mAttachInfo == null
                || (mAttachInfo.mSystemUiVisibility & SYSTEM_UI_LAYOUT_FLAGS) == 0) {
                || ((mAttachInfo.mSystemUiVisibility & SYSTEM_UI_LAYOUT_FLAGS) == 0
                && !mAttachInfo.mOverscanRequested)) {
            outLocalInsets.set(in.getSystemWindowInsets());
            return in.consumeSystemWindowInsets();
            return in.consumeSystemWindowInsets().inset(outLocalInsets);
        } else {
            outLocalInsets.set(0, 0, 0, 0);
            return in;
            // The application wants to take care of fitting system window for
            // the content...  however we still need to take care of any overscan here.
            final Rect overscan = mAttachInfo.mOverscanInsets;
            outLocalInsets.set(overscan);
            return in.inset(outLocalInsets);
        }
    }
+114 −3
Original line number Diff line number Diff line
@@ -20,6 +20,10 @@ package android.view;
import android.annotation.Nullable;
import android.graphics.Rect;

import com.android.internal.util.Preconditions;

import java.util.Objects;

/**
 * Describes a set of insets for window content.
 *
@@ -27,6 +31,12 @@ import android.graphics.Rect;
 * To adjust insets, use one of the supplied clone methods to obtain a new WindowInsets instance
 * with the adjusted properties.</p>
 *
 * <p>Note: Before {@link android.os.Build.VERSION_CODES#P P}, WindowInsets instances were only
 * immutable during a single layout pass (i.e. would return the same values between
 * {@link View#onApplyWindowInsets} and {@link View#onLayout}, but could return other values
 * otherwise). Starting with {@link android.os.Build.VERSION_CODES#P P}, WindowInsets are
 * always immutable and implement equality.
 *
 * @see View.OnApplyWindowInsetsListener
 * @see View#onApplyWindowInsets(WindowInsets)
 */
@@ -69,13 +79,14 @@ public final class WindowInsets {
    public WindowInsets(Rect systemWindowInsets, Rect windowDecorInsets, Rect stableInsets,
            boolean isRound, boolean alwaysConsumeNavBar, DisplayCutout displayCutout) {
        mSystemWindowInsetsConsumed = systemWindowInsets == null;
        mSystemWindowInsets = mSystemWindowInsetsConsumed ? EMPTY_RECT : systemWindowInsets;
        mSystemWindowInsets = mSystemWindowInsetsConsumed
                ? EMPTY_RECT : new Rect(systemWindowInsets);

        mWindowDecorInsetsConsumed = windowDecorInsets == null;
        mWindowDecorInsets = mWindowDecorInsetsConsumed ? EMPTY_RECT : windowDecorInsets;
        mWindowDecorInsets = mWindowDecorInsetsConsumed ? EMPTY_RECT : new Rect(windowDecorInsets);

        mStableInsetsConsumed = stableInsets == null;
        mStableInsets = mStableInsetsConsumed ? EMPTY_RECT : stableInsets;
        mStableInsets = mStableInsetsConsumed ? EMPTY_RECT : new Rect(stableInsets);

        mIsRound = isRound;
        mAlwaysConsumeNavBar = alwaysConsumeNavBar;
@@ -535,4 +546,104 @@ public final class WindowInsets {
                + (isRound() ? " round" : "")
                + "}";
    }

    /**
     * Returns a copy of this instance inset in the given directions.
     *
     * @see #inset(int, int, int, int)
     * @hide
     */
    public WindowInsets inset(Rect r) {
        return inset(r.left, r.top, r.right, r.bottom);
    }

    /**
     * Returns a copy of this instance inset in the given directions.
     *
     * This is intended for dispatching insets to areas of the window that are smaller than the
     * current area.
     *
     * <p>Example:
     * <pre>
     * childView.dispatchApplyWindowInsets(insets.inset(
     *         childMarginLeft, childMarginTop, childMarginBottom, childMarginRight));
     * </pre>
     *
     * @param left the amount of insets to remove from the left. Must be non-negative.
     * @param top the amount of insets to remove from the top. Must be non-negative.
     * @param right the amount of insets to remove from the right. Must be non-negative.
     * @param bottom the amount of insets to remove from the bottom. Must be non-negative.
     *
     * @return the inset insets
     *
     * @hide pending API
     */
    public WindowInsets inset(int left, int top, int right, int bottom) {
        Preconditions.checkArgumentNonnegative(left);
        Preconditions.checkArgumentNonnegative(top);
        Preconditions.checkArgumentNonnegative(right);
        Preconditions.checkArgumentNonnegative(bottom);

        WindowInsets result = new WindowInsets(this);
        if (!result.mSystemWindowInsetsConsumed) {
            result.mSystemWindowInsets =
                    insetInsets(result.mSystemWindowInsets, left, top, right, bottom);
        }
        if (!result.mWindowDecorInsetsConsumed) {
            result.mWindowDecorInsets =
                    insetInsets(result.mWindowDecorInsets, left, top, right, bottom);
        }
        if (!result.mStableInsetsConsumed) {
            result.mStableInsets = insetInsets(result.mStableInsets, left, top, right, bottom);
        }
        if (mDisplayCutout != null) {
            result.mDisplayCutout = result.mDisplayCutout.inset(left, top, right, bottom);
            if (result.mDisplayCutout.isEmpty()) {
                result.mDisplayCutout = null;
            }
        }
        return result;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || !(o instanceof WindowInsets)) return false;
        WindowInsets that = (WindowInsets) o;
        return mIsRound == that.mIsRound
                && mAlwaysConsumeNavBar == that.mAlwaysConsumeNavBar
                && mSystemWindowInsetsConsumed == that.mSystemWindowInsetsConsumed
                && mWindowDecorInsetsConsumed == that.mWindowDecorInsetsConsumed
                && mStableInsetsConsumed == that.mStableInsetsConsumed
                && mDisplayCutoutConsumed == that.mDisplayCutoutConsumed
                && Objects.equals(mSystemWindowInsets, that.mSystemWindowInsets)
                && Objects.equals(mWindowDecorInsets, that.mWindowDecorInsets)
                && Objects.equals(mStableInsets, that.mStableInsets)
                && Objects.equals(mDisplayCutout, that.mDisplayCutout);
    }

    @Override
    public int hashCode() {
        return Objects.hash(mSystemWindowInsets, mWindowDecorInsets, mStableInsets, mIsRound,
                mDisplayCutout, mAlwaysConsumeNavBar, mSystemWindowInsetsConsumed,
                mWindowDecorInsetsConsumed, mStableInsetsConsumed, mDisplayCutoutConsumed);
    }

    private static Rect insetInsets(Rect insets, int left, int top, int right, int bottom) {
        int newLeft = Math.max(0, insets.left - left);
        int newTop = Math.max(0, insets.top - top);
        int newRight = Math.max(0, insets.right - right);
        int newBottom = Math.max(0, insets.bottom - bottom);
        if (newLeft == left && newTop == top && newRight == right && newBottom == bottom) {
            return insets;
        }
        return new Rect(newLeft, newTop, newRight, newBottom);
    }

    /**
     * @return whether system window insets have been consumed.
     */
    boolean isSystemWindowInsetsConsumed() {
        return mSystemWindowInsetsConsumed;
    }
}
+8 −11
Original line number Diff line number Diff line
@@ -983,14 +983,14 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind
            if (attrs.height == WindowManager.LayoutParams.WRAP_CONTENT) {
                mFloatingInsets.top = insets.getSystemWindowInsetTop();
                mFloatingInsets.bottom = insets.getSystemWindowInsetBottom();
                insets = insets.replaceSystemWindowInsets(insets.getSystemWindowInsetLeft(), 0,
                        insets.getSystemWindowInsetRight(), 0);
                insets = insets.inset(0, insets.getSystemWindowInsetTop(),
                        0, insets.getSystemWindowInsetBottom());
            }
            if (mWindow.getAttributes().width == WindowManager.LayoutParams.WRAP_CONTENT) {
                mFloatingInsets.left = insets.getSystemWindowInsetTop();
                mFloatingInsets.right = insets.getSystemWindowInsetBottom();
                insets = insets.replaceSystemWindowInsets(0, insets.getSystemWindowInsetTop(),
                        0, insets.getSystemWindowInsetBottom());
                insets = insets.inset(insets.getSystemWindowInsetLeft(), 0,
                        insets.getSystemWindowInsetRight(), 0);
            }
        }
        mFrameOffsets.set(insets.getSystemWindowInsets());
@@ -1158,11 +1158,7 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind
                }
            }
            if (insets != null) {
                insets = insets.replaceSystemWindowInsets(
                        insets.getSystemWindowInsetLeft() - consumedLeft,
                        insets.getSystemWindowInsetTop() - consumedTop,
                        insets.getSystemWindowInsetRight() - consumedRight,
                        insets.getSystemWindowInsetBottom() - consumedBottom);
                insets = insets.inset(consumedLeft, consumedTop, consumedRight, consumedBottom);
            }
        }

@@ -1383,8 +1379,9 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind
                    // screen_simple_overlay_action_mode.xml).
                    final boolean nonOverlay = (mWindow.getLocalFeaturesPrivate()
                            & (1 << Window.FEATURE_ACTION_MODE_OVERLAY)) == 0;
                    insets = insets.consumeSystemWindowInsets(
                            false, nonOverlay && showStatusGuard /* top */, false, false);
                    if (nonOverlay && showStatusGuard) {
                        insets = insets.inset(0, insets.getSystemWindowInsetTop(), 0, 0);
                    }
                } else {
                    // reset top margin
                    if (mlp.topMargin != 0) {
+23 −13
Original line number Diff line number Diff line
@@ -75,10 +75,10 @@ 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 mBaseInnerInsets = new Rect();
    private final Rect mLastBaseInnerInsets = new Rect();
    private final Rect mInnerInsets = new Rect();
    private final Rect mLastInnerInsets = new Rect();
    private WindowInsets mBaseInnerInsets = WindowInsets.CONSUMED;
    private WindowInsets mLastBaseInnerInsets = WindowInsets.CONSUMED;
    private WindowInsets mInnerInsets = WindowInsets.CONSUMED;
    private WindowInsets mLastInnerInsets = WindowInsets.CONSUMED;

    private ActionBarVisibilityCallback mActionBarVisibilityCallback;

@@ -322,11 +322,14 @@ public class ActionBarOverlayLayout extends ViewGroup implements DecorContentPar
            changed |= applyInsets(mActionBarBottom, systemInsets, true, false, true, true);
        }

        mBaseInnerInsets.set(systemInsets);
        computeFitSystemWindows(mBaseInnerInsets, mBaseContentInsets);
        // Cannot use the result of computeSystemWindowInsets, because that consumes the
        // systemWindowInsets. Instead, we do the insetting by the local insets ourselves.
        computeSystemWindowInsets(insets, mBaseContentInsets);
        mBaseInnerInsets = insets.inset(mBaseContentInsets);

        if (!mLastBaseInnerInsets.equals(mBaseInnerInsets)) {
            changed = true;
            mLastBaseContentInsets.set(mBaseContentInsets);
            mLastBaseInnerInsets = mBaseInnerInsets;
        }
        if (!mLastBaseContentInsets.equals(mBaseContentInsets)) {
            changed = true;
@@ -430,22 +433,29 @@ public class ActionBarOverlayLayout extends ViewGroup implements DecorContentPar
        // will still be covered by the action bar if they have requested it to
        // overlay.
        mContentInsets.set(mBaseContentInsets);
        mInnerInsets.set(mBaseInnerInsets);
        mInnerInsets = mBaseInnerInsets;
        if (!mOverlayMode && !stable) {
            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 {
            mInnerInsets.top += topInset;
            mInnerInsets.bottom += bottomInset;
            // 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 (!mLastInnerInsets.equals(mInnerInsets)) {
            // If the inner insets have changed, we need to dispatch this down to
            // the app's fitSystemWindows().  We do this before measuring the content
            // the app's onApplyWindowInsets().  We do this before measuring the content
            // view to keep the same semantics as the normal fitSystemWindows() call.
            mLastInnerInsets.set(mInnerInsets);
            mContent.dispatchApplyWindowInsets(new WindowInsets(mInnerInsets));
            mLastInnerInsets = mInnerInsets;
            mContent.dispatchApplyWindowInsets(mInnerInsets);
        }

        measureChildWithMargins(mContent, widthMeasureSpec, 0, heightMeasureSpec, 0);
Loading