From f7b7426d81c5365473d00362cf158aa5ae35cee3 Mon Sep 17 00:00:00 2001 From: Adrian Roos Date: Wed, 22 Nov 2017 14:21:01 +0100 Subject: [PATCH] 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 --- config/hiddenapi-light-greylist.txt | 1 + core/java/android/view/View.java | 42 ++-- core/java/android/view/WindowInsets.java | 117 ++++++++++- .../com/android/internal/policy/DecorView.java | 19 +- .../internal/widget/ActionBarOverlayLayout.java | 36 ++-- .../widget/ActionBarOverlayLayoutTest.java | 226 +++++++++++++++++++++ .../android/server/wm/ScreenDecorWindowTests.java | 7 +- 7 files changed, 398 insertions(+), 50 deletions(-) create mode 100644 core/tests/coretests/src/com/android/internal/widget/ActionBarOverlayLayoutTest.java diff --git a/config/hiddenapi-light-greylist.txt b/config/hiddenapi-light-greylist.txt index cecd56404372..2663cdb68f5b 100644 --- a/config/hiddenapi-light-greylist.txt +++ b/config/hiddenapi-light-greylist.txt @@ -6175,6 +6175,7 @@ Landroid/view/WindowContentFrameStats;->init(J[J[J[J)V Landroid/view/WindowInsets;->(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;->(Ljava/lang/String;)V Landroid/view/WindowManager$LayoutParams;->backup()V Landroid/view/WindowManager$LayoutParams;->FLAG_SLIPPERY:I diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 380707996436..9afeb075b455 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -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); } } diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java index e5cbe96b9173..fbd8141ae27a 100644 --- a/core/java/android/view/WindowInsets.java +++ b/core/java/android/view/WindowInsets.java @@ -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.

* + *

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. + * + *

Example: + *

+     * childView.dispatchApplyWindowInsets(insets.inset(
+     *         childMarginLeft, childMarginTop, childMarginBottom, childMarginRight));
+     * 
+ * + * @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; + } } diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java index 2db573918e8a..465957d7cfd0 100644 --- a/core/java/com/android/internal/policy/DecorView.java +++ b/core/java/com/android/internal/policy/DecorView.java @@ -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) { diff --git a/core/java/com/android/internal/widget/ActionBarOverlayLayout.java b/core/java/com/android/internal/widget/ActionBarOverlayLayout.java index 5d7fa6a742be..4a1c95532ba0 100644 --- a/core/java/com/android/internal/widget/ActionBarOverlayLayout.java +++ b/core/java/com/android/internal/widget/ActionBarOverlayLayout.java @@ -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); diff --git a/core/tests/coretests/src/com/android/internal/widget/ActionBarOverlayLayoutTest.java b/core/tests/coretests/src/com/android/internal/widget/ActionBarOverlayLayoutTest.java new file mode 100644 index 000000000000..cac4e88c8edc --- /dev/null +++ b/core/tests/coretests/src/com/android/internal/widget/ActionBarOverlayLayoutTest.java @@ -0,0 +1,226 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.widget; + +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 org.hamcrest.CoreMatchers.nullValue; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThat; + +import android.content.Context; +import android.graphics.Rect; +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; +import android.view.DisplayCutout; +import android.view.View; +import android.view.View.OnApplyWindowInsetsListener; +import android.view.ViewGroup; +import android.view.WindowInsets; +import android.widget.FrameLayout; +import android.widget.Toolbar; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.lang.reflect.Field; +import java.util.Arrays; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class ActionBarOverlayLayoutTest { + + private static final Rect TOP_INSET_5 = new Rect(0, 5, 0, 0); + private static final Rect TOP_INSET_25 = new Rect(0, 25, 0, 0); + private static final Rect ZERO_INSET = new Rect(0, 0, 0, 0); + private static final DisplayCutout CONSUMED_CUTOUT = null; + private static final DisplayCutout CUTOUT_5 = new DisplayCutout(TOP_INSET_5, Arrays.asList( + new Rect(100, 0, 200, 5))); + private static final int EXACTLY_1000 = makeMeasureSpec(1000, EXACTLY); + + private Context mContext; + private TestActionBarOverlayLayout mLayout; + + private ViewGroup mContent; + private ViewGroup mActionBarTop; + private Toolbar mToolbar; + private FakeOnApplyWindowListener mContentInsetsListener; + + + @Before + public void setUp() throws Exception { + mContext = InstrumentationRegistry.getContext(); + mLayout = new TestActionBarOverlayLayout(mContext); + mLayout.makeOptionalFitsSystemWindows(); + + mContent = createViewGroupWithId(com.android.internal.R.id.content); + mContent.setLayoutParams(new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)); + mLayout.addView(mContent); + + mContentInsetsListener = new FakeOnApplyWindowListener(); + mContent.setOnApplyWindowInsetsListener(mContentInsetsListener); + + mActionBarTop = new ActionBarContainer(mContext); + mActionBarTop.setId(com.android.internal.R.id.action_bar_container); + mActionBarTop.setLayoutParams(new ViewGroup.LayoutParams(MATCH_PARENT, 20)); + mLayout.addView(mActionBarTop); + mLayout.setActionBarHeight(20); + + mToolbar = new Toolbar(mContext); + mToolbar.setId(com.android.internal.R.id.action_bar); + mActionBarTop.addView(mToolbar); + } + + @Test + public void topInset_consumedCutout_stable() { + mLayout.setStable(true); + mLayout.dispatchApplyWindowInsets(insetsWith(TOP_INSET_5, CONSUMED_CUTOUT)); + + assertThat(mContentInsetsListener.captured, nullValue()); + + mLayout.measure(EXACTLY_1000, EXACTLY_1000); + + // Action bar height is added to the top inset + assertThat(mContentInsetsListener.captured, is(insetsWith(TOP_INSET_25, CONSUMED_CUTOUT))); + } + + @Test + public void topInset_consumedCutout_notStable() { + mLayout.dispatchApplyWindowInsets(insetsWith(TOP_INSET_5, CONSUMED_CUTOUT)); + + assertThat(mContentInsetsListener.captured, nullValue()); + + mLayout.measure(EXACTLY_1000, EXACTLY_1000); + + assertThat(mContentInsetsListener.captured, is(insetsWith(ZERO_INSET, CONSUMED_CUTOUT))); + } + + @Test + public void topInset_noCutout_stable() { + mLayout.setStable(true); + mLayout.dispatchApplyWindowInsets(insetsWith(TOP_INSET_5, NO_CUTOUT)); + + assertThat(mContentInsetsListener.captured, nullValue()); + + mLayout.measure(EXACTLY_1000, EXACTLY_1000); + + // Action bar height is added to the top inset + assertThat(mContentInsetsListener.captured, is(insetsWith(TOP_INSET_25, NO_CUTOUT))); + } + + @Test + public void topInset_noCutout_notStable() { + mLayout.dispatchApplyWindowInsets(insetsWith(TOP_INSET_5, NO_CUTOUT)); + + assertThat(mContentInsetsListener.captured, nullValue()); + + mLayout.measure(EXACTLY_1000, EXACTLY_1000); + + assertThat(mContentInsetsListener.captured, is(insetsWith(ZERO_INSET, NO_CUTOUT))); + } + + @Test + public void topInset_cutout_stable() { + mLayout.setStable(true); + mLayout.dispatchApplyWindowInsets(insetsWith(TOP_INSET_5, CUTOUT_5)); + + assertThat(mContentInsetsListener.captured, nullValue()); + + mLayout.measure(EXACTLY_1000, EXACTLY_1000); + + // Action bar height is added to the top inset + assertThat(mContentInsetsListener.captured, is(insetsWith(TOP_INSET_25, CUTOUT_5))); + } + + @Test + public void topInset_cutout_notStable() { + mLayout.dispatchApplyWindowInsets(insetsWith(TOP_INSET_5, CUTOUT_5)); + + assertThat(mContentInsetsListener.captured, nullValue()); + + mLayout.measure(EXACTLY_1000, EXACTLY_1000); + + assertThat(mContentInsetsListener.captured, is(insetsWith(ZERO_INSET, NO_CUTOUT))); + } + + private WindowInsets insetsWith(Rect content, DisplayCutout cutout) { + return new WindowInsets(content, null, null, false, false, cutout); + } + + private ViewGroup createViewGroupWithId(int id) { + final FrameLayout v = new FrameLayout(mContext); + v.setId(id); + return v; + } + + static class TestActionBarOverlayLayout extends ActionBarOverlayLayout { + private boolean mStable; + + public TestActionBarOverlayLayout(Context context) { + super(context); + } + + @Override + public WindowInsets computeSystemWindowInsets(WindowInsets in, Rect outLocalInsets) { + if (mStable) { + // Emulate the effect of makeOptionalFitsSystemWindows, because we can't do that + // without being attached to a window. + outLocalInsets.setEmpty(); + return in; + } + return super.computeSystemWindowInsets(in, outLocalInsets); + } + + void setStable(boolean stable) { + mStable = stable; + setSystemUiVisibility(stable ? SYSTEM_UI_FLAG_LAYOUT_STABLE : 0); + } + + @Override + public int getWindowSystemUiVisibility() { + return getSystemUiVisibility(); + } + + void setActionBarHeight(int height) { + try { + final Field field = ActionBarOverlayLayout.class.getDeclaredField( + "mActionBarHeight"); + field.setAccessible(true); + field.setInt(this, height); + } catch (ReflectiveOperationException e) { + throw new RuntimeException(e); + } + } + } + + static class FakeOnApplyWindowListener implements OnApplyWindowInsetsListener { + WindowInsets captured; + + @Override + public WindowInsets onApplyWindowInsets(View v, WindowInsets insets) { + assertNotNull(insets); + captured = insets; + return v.onApplyWindowInsets(insets); + } + } +} diff --git a/services/tests/servicestests/src/com/android/server/wm/ScreenDecorWindowTests.java b/services/tests/servicestests/src/com/android/server/wm/ScreenDecorWindowTests.java index 24d925f1bc95..a2ccee46e0c9 100644 --- a/services/tests/servicestests/src/com/android/server/wm/ScreenDecorWindowTests.java +++ b/services/tests/servicestests/src/com/android/server/wm/ScreenDecorWindowTests.java @@ -159,7 +159,12 @@ public class ScreenDecorWindowTests { updateWindow(decorWindow, TOP, MATCH_PARENT, mDecorThickness, 0, PRIVATE_FLAG_IS_SCREEN_DECOR); - assertTopInsetEquals(mTestActivity, initialInsets.getSystemWindowInsetTop()); + + // TODO: fix test and re-enable assertion. + // initialInsets was not actually immutable and just updated to the current insets, + // meaning this assertion never actually tested anything. Now that WindowInsets actually is + // immutable, it turns out the test was broken. + // assertTopInsetEquals(mTestActivity, initialInsets.getSystemWindowInsetTop()); updateWindow(decorWindow, TOP, MATCH_PARENT, mDecorThickness, PRIVATE_FLAG_IS_SCREEN_DECOR, PRIVATE_FLAG_IS_SCREEN_DECOR); -- 2.11.0