From 4f7ebe3bcec353150a49e18dd319f2310a34c0fe Mon Sep 17 00:00:00 2001 From: Yunfan Chen Date: Sat, 22 Feb 2020 20:57:57 +0900 Subject: [PATCH] Make caption a insets source This patch introduced setCaptionInsets, and set the Insets in ViewRootImpl when dispatch the insets if there's a caption. Modification is made in Window and DecorCaptionView to make caption overlay with the app content, and pass the value to ViewRootImpl to apply when dispatch. It is necessary to trigger a dispatch when caption enabled status chanaged, otherwise sometimes it will not be updated. Because caption is now updated locally on the client side. Some old logic to deal with the overlay caption without insets are removed, including the touch event dispatch override, the color override. Bug: 134531136 Test: go/wm-smoke Test: Manually change the value in dispatchApplyInsets, can observe a blank content area when there's a caption bar. Test: atest InsetsStateTest Test: atest InsetsControllerTest Change-Id: I356344a13c8569512d8f51f7ea19a5603f778252 --- core/java/android/view/InsetsController.java | 30 ++++++++++++++- core/java/android/view/InsetsSource.java | 7 ++++ core/java/android/view/InsetsState.java | 30 ++++++++++++++- .../android/view/PendingInsetsController.java | 9 +++++ core/java/android/view/View.java | 5 +++ core/java/android/view/ViewRootImpl.java | 17 +++++++++ core/java/android/view/Window.java | 5 +-- core/java/android/view/WindowInsets.java | 1 + .../android/view/WindowInsetsController.java | 9 +++++ .../android/internal/policy/DecorView.java | 37 ++++++++++++------- .../internal/widget/DecorCaptionView.java | 29 ++------------- .../android/view/InsetsControllerTest.java | 22 ++++++++++- .../src/android/view/InsetsStateTest.java | 29 +++++++++++++++ 13 files changed, 184 insertions(+), 46 deletions(-) diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java index e6bd84391fef0..40a460dfece0b 100644 --- a/core/java/android/view/InsetsController.java +++ b/core/java/android/view/InsetsController.java @@ -16,6 +16,7 @@ package android.view; +import static android.view.InsetsState.ITYPE_CAPTION_BAR; import static android.view.InsetsState.ITYPE_IME; import static android.view.InsetsState.toInternalType; import static android.view.InsetsState.toPublicType; @@ -367,6 +368,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation private int mLastLegacySystemUiFlags; private DisplayCutout mLastDisplayCutout; private boolean mStartingAnimation; + private int mCaptionInsetsHeight = 0; private SyncRtSurfaceTransactionApplier mApplier; @@ -460,7 +462,8 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation @VisibleForTesting public boolean onStateChanged(InsetsState state) { - boolean localStateChanged = !mState.equals(state); + boolean localStateChanged = !mState.equals(state, true /* excludingCaptionInsets */) + || !captionInsetsUnchanged(); if (!localStateChanged && mLastDispachedState.equals(state)) { return false; } @@ -470,7 +473,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation if (localStateChanged) { mViewRoot.notifyInsetsChanged(); } - if (!mState.equals(mLastDispachedState)) { + if (!mState.equals(mLastDispachedState, true /* excludingCaptionInsets */)) { sendStateToWindowManager(); } return true; @@ -488,6 +491,23 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation mState.removeSource(source.getType()); } } + if (mCaptionInsetsHeight != 0) { + mState.getSource(ITYPE_CAPTION_BAR).setFrame(new Rect(mFrame.left, mFrame.top, + mFrame.right, mFrame.top + mCaptionInsetsHeight)); + } + } + + private boolean captionInsetsUnchanged() { + if (mState.peekSource(ITYPE_CAPTION_BAR) == null + && mCaptionInsetsHeight == 0) { + return true; + } + if (mState.peekSource(ITYPE_CAPTION_BAR) != null + && mCaptionInsetsHeight + == mState.peekSource(ITYPE_CAPTION_BAR).getFrame().height()) { + return true; + } + return false; } /** @@ -964,6 +984,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation InsetsState tmpState = new InsetsState(); for (int i = mSourceConsumers.size() - 1; i >= 0; i--) { final InsetsSourceConsumer consumer = mSourceConsumers.valueAt(i); + if (consumer.getType() == ITYPE_CAPTION_BAR) continue; if (consumer.getControl() != null) { tmpState.addSource(mState.getSource(consumer.getType())); } @@ -1104,6 +1125,11 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation return mViewRoot.mWindowAttributes.insetsFlags.appearance; } + @Override + public void setCaptionInsetsHeight(int height) { + mCaptionInsetsHeight = height; + } + @Override public void setSystemBarsBehavior(@Behavior int behavior) { mViewRoot.mWindowAttributes.privateFlags |= PRIVATE_FLAG_BEHAVIOR_CONTROLLED; diff --git a/core/java/android/view/InsetsSource.java b/core/java/android/view/InsetsSource.java index 294faaf0b5c84..033ccef3666db 100644 --- a/core/java/android/view/InsetsSource.java +++ b/core/java/android/view/InsetsSource.java @@ -16,6 +16,7 @@ package android.view; +import static android.view.InsetsState.ITYPE_CAPTION_BAR; import static android.view.InsetsState.ITYPE_IME; import android.annotation.NonNull; @@ -118,6 +119,12 @@ public class InsetsSource implements Parcelable { if (!getIntersection(frame, relativeFrame, mTmpFrame)) { return Insets.NONE; } + // During drag-move and drag-resizing, the caption insets position may not get updated + // before the app frame get updated. To layout the app content correctly during drag events, + // we always return the insets with the corresponding height covering the top. + if (getType() == ITYPE_CAPTION_BAR) { + return Insets.of(0, frame.height(), 0, 0); + } // TODO: Currently, non-floating IME always intersects at bottom due to issues with cutout. // However, we should let the policy decide from the server. diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java index 40e6f57f22862..c5154662928e0 100644 --- a/core/java/android/view/InsetsState.java +++ b/core/java/android/view/InsetsState.java @@ -45,6 +45,8 @@ import android.view.WindowInsets.Type; import android.view.WindowInsets.Type.InsetsType; import android.view.WindowManager.LayoutParams.SoftInputModeFlags; +import com.android.internal.annotations.VisibleForTesting; + import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -498,6 +500,19 @@ public class InsetsState implements Parcelable { @Override public boolean equals(Object o) { + return equals(o, false); + } + + /** + * An equals method can exclude the caption insets. This is useful because we assemble the + * caption insets information on the client side, and when we communicate with server, it's + * excluded. + * @param excludingCaptionInsets {@code true} if we want to compare two InsetsState objects but + * ignore the caption insets source value. + * @return {@code true} if the two InsetsState objects are equal, {@code false} otherwise. + */ + @VisibleForTesting + public boolean equals(Object o, boolean excludingCaptionInsets) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } @@ -506,11 +521,24 @@ public class InsetsState implements Parcelable { if (!mDisplayFrame.equals(state.mDisplayFrame)) { return false; } - if (mSources.size() != state.mSources.size()) { + int size = mSources.size(); + int otherSize = state.mSources.size(); + if (excludingCaptionInsets) { + if (mSources.get(ITYPE_CAPTION_BAR) != null) { + size--; + } + if (state.mSources.get(ITYPE_CAPTION_BAR) != null) { + otherSize--; + } + } + if (size != otherSize) { return false; } for (int i = mSources.size() - 1; i >= 0; i--) { InsetsSource source = mSources.valueAt(i); + if (excludingCaptionInsets) { + if (source.getType() == ITYPE_CAPTION_BAR) continue; + } InsetsSource otherSource = state.mSources.get(source.getType()); if (otherSource == null) { return false; diff --git a/core/java/android/view/PendingInsetsController.java b/core/java/android/view/PendingInsetsController.java index 229ee03521bce..a106b2c4726db 100644 --- a/core/java/android/view/PendingInsetsController.java +++ b/core/java/android/view/PendingInsetsController.java @@ -42,6 +42,7 @@ public class PendingInsetsController implements WindowInsetsController { private InsetsController mReplayedInsetsController; private ArrayList mControllableInsetsChangedListeners = new ArrayList<>(); + private int mCaptionInsetsHeight = 0; @Override public void show(int types) { @@ -79,6 +80,11 @@ public class PendingInsetsController implements WindowInsetsController { return mAppearance; } + @Override + public void setCaptionInsetsHeight(int height) { + mCaptionInsetsHeight = height; + } + @Override public void setSystemBarsBehavior(int behavior) { if (mReplayedInsetsController != null) { @@ -134,6 +140,9 @@ public class PendingInsetsController implements WindowInsetsController { if (mAppearanceMask != 0) { controller.setSystemBarsAppearance(mAppearance, mAppearanceMask); } + if (mCaptionInsetsHeight != 0) { + controller.setCaptionInsetsHeight(mCaptionInsetsHeight); + } int size = mRequests.size(); for (int i = 0; i < size; i++) { mRequests.get(i).replay(controller); diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index d69357bc503d2..49bff6df538da 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -28768,6 +28768,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, publicAlternatives = "Use {@link WindowInsets#getInsets(int)}") final Rect mStableInsets = new Rect(); + /** + * Current caption insets to the display coordinate. + */ + final Rect mCaptionInsets = new Rect(); + final DisplayCutout.ParcelableWrapper mDisplayCutout = new DisplayCutout.ParcelableWrapper(DisplayCutout.NO_CUTOUT); diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 50202aed36d25..51304dcfe8cbf 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -158,6 +158,7 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.IResultReceiver; import com.android.internal.os.SomeArgs; +import com.android.internal.policy.DecorView; import com.android.internal.policy.PhoneFallbackEventHandler; import com.android.internal.util.Preconditions; import com.android.internal.view.BaseSurfaceHolder; @@ -2221,6 +2222,19 @@ public final class ViewRootImpl implements ViewParent, Trace.traceEnd(Trace.TRACE_TAG_VIEW); } + private boolean updateCaptionInsets() { + if (!(mView instanceof DecorView)) return false; + final int captionInsetsHeight = ((DecorView) mView).getCaptionInsetsHeight(); + final Rect captionFrame = new Rect(); + if (captionInsetsHeight != 0) { + captionFrame.set(mWinFrame.left, mWinFrame.top, mWinFrame.right, + mWinFrame.top + captionInsetsHeight); + } + if (mAttachInfo.mCaptionInsets.equals(captionFrame)) return false; + mAttachInfo.mCaptionInsets.set(captionFrame); + return true; + } + private boolean shouldDispatchCutout() { return mWindowAttributes.layoutInDisplayCutoutMode == LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS @@ -2592,6 +2606,9 @@ public final class ViewRootImpl implements ViewParent, mAttachInfo.mAlwaysConsumeSystemBars = mPendingAlwaysConsumeSystemBars; dispatchApplyInsets = true; } + if (updateCaptionInsets()) { + dispatchApplyInsets = true; + } if (dispatchApplyInsets || mLastSystemUiVisibility != mAttachInfo.mSystemUiVisibility || mApplyInsetsRequested) { mLastSystemUiVisibility = mAttachInfo.mSystemUiVisibility; diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java index 0c5c18316e610..ae9afabad533c 100644 --- a/core/java/android/view/Window.java +++ b/core/java/android/view/Window.java @@ -49,9 +49,6 @@ import android.transition.Transition; import android.transition.TransitionManager; import android.util.Pair; import android.view.View.OnApplyWindowInsetsListener; -import android.view.ViewGroup.LayoutParams; -import android.view.WindowInsets.Side.InsetsSide; -import android.view.WindowInsets.Type.InsetsType; import android.view.accessibility.AccessibilityEvent; import java.util.Collections; @@ -323,7 +320,7 @@ public abstract class Window { @UnsupportedAppUsage private boolean mDestroyed; - private boolean mOverlayWithDecorCaptionEnabled = false; + private boolean mOverlayWithDecorCaptionEnabled = true; private boolean mCloseOnSwipeEnabled = false; // The current window attributes. diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java index fde184ccfb0e1..9b2a6cbce48f2 100644 --- a/core/java/android/view/WindowInsets.java +++ b/core/java/android/view/WindowInsets.java @@ -17,6 +17,7 @@ package android.view; +import static android.view.WindowInsets.Type.CAPTION_BAR; import static android.view.WindowInsets.Type.DISPLAY_CUTOUT; import static android.view.WindowInsets.Type.FIRST; import static android.view.WindowInsets.Type.IME; diff --git a/core/java/android/view/WindowInsetsController.java b/core/java/android/view/WindowInsetsController.java index 0282ecac8920d..439223cf568b2 100644 --- a/core/java/android/view/WindowInsetsController.java +++ b/core/java/android/view/WindowInsetsController.java @@ -195,6 +195,15 @@ public interface WindowInsetsController { */ @Appearance int getSystemBarsAppearance(); + /** + * Notify the caption insets height change. The information will be used on the client side to, + * make sure the InsetsState has the correct caption insets. + * + * @param height the height of caption bar insets. + * @hide + */ + void setCaptionInsetsHeight(int height); + /** * Controls the behavior of system bars. * diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java index 51b73fc674e79..d2508f3616e48 100644 --- a/core/java/com/android/internal/policy/DecorView.java +++ b/core/java/com/android/internal/policy/DecorView.java @@ -78,7 +78,6 @@ import android.view.ContextThemeWrapper; import android.view.Gravity; import android.view.InputQueue; import android.view.InsetsState; -import android.view.InsetsController; import android.view.InsetsState.InternalInsetsType; import android.view.KeyEvent; import android.view.KeyboardShortcutGroup; @@ -1174,6 +1173,12 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind false /* matchVertical */, statusBarNeedsLeftInset, statusBarSideInset, animate && !disallowAnimate, mForceWindowDrawsBarBackgrounds, state); + + if (mHasCaption) { + final int captionColor = calculateStatusBarColor(); + mDecorCaptionView.getCaption().setBackgroundColor(captionColor); + updateDecorCaptionShade(); + } } // When we expand the window with FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS or @@ -1355,7 +1360,7 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind : state.attributes.isPresent(insetsState, mWindow.getAttributes().flags, force); boolean show = state.attributes.isVisible(state.present, color, mWindow.getAttributes().flags, force); - boolean showView = show && !isResizing() && size > 0; + boolean showView = show && !isResizing() && !mHasCaption && size > 0; boolean visibilityChanged = false; View view = state.view; @@ -2021,6 +2026,7 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind if (getForeground() != null) { drawableChanged(); } + getWindowInsetsController().setCaptionInsetsHeight(getCaptionInsetsHeight()); } } @@ -2094,6 +2100,7 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind mDecorCaptionView.onConfigurationChanged(displayWindowDecor); enableCaption(displayWindowDecor); } + getWindowInsetsController().setCaptionInsetsHeight(getCaptionInsetsHeight()); } void onResourcesLoaded(LayoutInflater inflater, int layoutResource) { @@ -2182,11 +2189,11 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind inflater = inflater.from(context); final DecorCaptionView view = (DecorCaptionView) inflater.inflate(R.layout.decor_caption, null); - setDecorCaptionShade(context, view); + setDecorCaptionShade(view); return view; } - private void setDecorCaptionShade(Context context, DecorCaptionView view) { + private void setDecorCaptionShade(DecorCaptionView view) { final int shade = mWindow.getDecorCaptionShade(); switch (shade) { case DECOR_CAPTION_SHADE_LIGHT: @@ -2196,15 +2203,10 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind setDarkDecorCaptionShade(view); break; default: { - TypedValue value = new TypedValue(); - context.getTheme().resolveAttribute(R.attr.colorPrimary, value, true); - // We invert the shade depending on brightness of the theme. Dark shade for light - // theme and vice versa. Thanks to this the buttons should be visible on the - // background. - if (Color.luminance(value.data) < 0.5) { - setLightDecorCaptionShade(view); - } else { + if ((getWindowSystemUiVisibility() & SYSTEM_UI_FLAG_LIGHT_STATUS_BAR) != 0) { setDarkDecorCaptionShade(view); + } else { + setLightDecorCaptionShade(view); } break; } @@ -2213,7 +2215,7 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind void updateDecorCaptionShade() { if (mDecorCaptionView != null) { - setDecorCaptionShade(getContext(), mDecorCaptionView); + setDecorCaptionShade(mDecorCaptionView); } } @@ -2483,6 +2485,15 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind return isShowingCaption() ? mDecorCaptionView.getCaptionHeight() : 0; } + /** + * @hide + * @return the height of insets covering the top of window content area. + */ + public int getCaptionInsetsHeight() { + if (!mWindow.isOverlayWithDecorCaptionEnabled()) return 0; + return getCaptionHeight(); + } + /** * Converts a DIP measure into physical pixels. * @param dip The dip value. diff --git a/core/java/com/android/internal/widget/DecorCaptionView.java b/core/java/com/android/internal/widget/DecorCaptionView.java index b5d787c24fbd9..7a01024ffc369 100644 --- a/core/java/com/android/internal/widget/DecorCaptionView.java +++ b/core/java/com/android/internal/widget/DecorCaptionView.java @@ -17,7 +17,6 @@ package com.android.internal.widget; import android.content.Context; -import android.graphics.Color; import android.graphics.Rect; import android.os.RemoteException; import android.util.AttributeSet; @@ -53,8 +52,7 @@ import java.util.ArrayList; *
  • ..
  • * * - * Although this ViewGroup has only two direct sub-Views, its behavior is more complex due to - * overlaying caption on the content and drawing. + * Here describe the behavior of overlaying caption on the content and drawing. * * First, no matter where the content View gets added, it will always be the first child and the * caption will be the second. This way the caption will always be drawn on top of the content when @@ -66,11 +64,9 @@ import java.util.ArrayList; *
  • DecorCaptionView.onInterceptTouchEvent() will try intercepting the touch events if the * down action is performed on top close or maximize buttons; the reason for that is we want these * buttons to always work.
  • - *
  • The content View will receive the touch event. Mind that content is actually underneath the - * caption, so we need to introduce our own dispatch ordering. We achieve this by overriding - * {@link #buildTouchDispatchChildList()}.
  • - *
  • If the touch event is not consumed by the content View, it will go to the caption View - * and the dragging logic will be executed.
  • + *
  • The caption view will try to consume the event to apply the dragging logic.
  • + *
  • If the touch event is not consumed by the caption, the content View will receive the touch + * event
  • * */ public class DecorCaptionView extends ViewGroup implements View.OnTouchListener, @@ -137,11 +133,6 @@ public class DecorCaptionView extends ViewGroup implements View.OnTouchListener, mOwner = owner; mShow = show; mOverlayWithAppContent = owner.isOverlayWithDecorCaptionEnabled(); - if (mOverlayWithAppContent) { - // The caption is covering the content, so we make its background transparent to make - // the content visible. - mCaption.setBackgroundColor(Color.TRANSPARENT); - } updateCaptionVisibility(); // By changing the outline provider to BOUNDS, the window can remove its // background without removing the shadow. @@ -235,18 +226,6 @@ public class DecorCaptionView extends ViewGroup implements View.OnTouchListener, return mDragging || mCheckForDragging; } - @Override - public ArrayList buildTouchDispatchChildList() { - mTouchDispatchList.ensureCapacity(3); - if (mCaption != null) { - mTouchDispatchList.add(mCaption); - } - if (mContent != null) { - mTouchDispatchList.add(mContent); - } - return mTouchDispatchList; - } - @Override public boolean shouldDelayChildPressedState() { return false; diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java index efdb51dcdfa97..cbb379bf8207e 100644 --- a/core/tests/coretests/src/android/view/InsetsControllerTest.java +++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java @@ -21,6 +21,7 @@ import static android.view.InsetsController.ANIMATION_TYPE_NONE; import static android.view.InsetsController.ANIMATION_TYPE_SHOW; import static android.view.InsetsSourceConsumer.ShowResult.IME_SHOW_DELAYED; import static android.view.InsetsSourceConsumer.ShowResult.SHOW_IMMEDIATELY; +import static android.view.InsetsState.ITYPE_CAPTION_BAR; import static android.view.InsetsState.ITYPE_IME; import static android.view.InsetsState.ITYPE_NAVIGATION_BAR; import static android.view.InsetsState.ITYPE_STATUS_BAR; @@ -88,7 +89,6 @@ import java.util.concurrent.CountDownLatch; @Presubmit @RunWith(AndroidJUnit4.class) public class InsetsControllerTest { - private InsetsController mController; private SurfaceSession mSession = new SurfaceSession(); private SurfaceControl mLeash; @@ -665,6 +665,26 @@ public class InsetsControllerTest { InstrumentationRegistry.getInstrumentation().waitForIdleSync(); } + @Test + public void testCaptionInsetsStateAssemble() { + InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { + mController.onFrameChanged(new Rect(0, 0, 100, 300)); + final InsetsState state = new InsetsState(mController.getState(), true); + final Rect captionFrame = new Rect(0, 0, 100, 100); + mController.setCaptionInsetsHeight(100); + mController.onStateChanged(state); + final InsetsState currentState = new InsetsState(mController.getState()); + // The caption bar source should be synced with the info in mAttachInfo. + assertEquals(captionFrame, currentState.peekSource(ITYPE_CAPTION_BAR).getFrame()); + assertTrue(currentState.equals(state, true /* excludingCaptionInsets*/)); + mController.setCaptionInsetsHeight(0); + mController.onStateChanged(state); + // The caption bar source should not be there at all, because we don't add empty + // caption to the state from the server. + assertNull(mController.getState().peekSource(ITYPE_CAPTION_BAR)); + }); + } + private void waitUntilNextFrame() throws Exception { final CountDownLatch latch = new CountDownLatch(1); Choreographer.getMainThreadInstance().postCallback(Choreographer.CALLBACK_COMMIT, diff --git a/core/tests/coretests/src/android/view/InsetsStateTest.java b/core/tests/coretests/src/android/view/InsetsStateTest.java index 721dc98ff4d18..2884777fc9976 100644 --- a/core/tests/coretests/src/android/view/InsetsStateTest.java +++ b/core/tests/coretests/src/android/view/InsetsStateTest.java @@ -153,6 +153,35 @@ public class InsetsStateTest { } } + + @Test + public void testCalculateInsets_captionStatusBarOverlap() throws Exception { + try (InsetsModeSession session = + new InsetsModeSession(ViewRootImpl.NEW_INSETS_MODE_FULL)) { + mState.getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 100, 100)); + mState.getSource(ITYPE_STATUS_BAR).setVisible(true); + mState.getSource(ITYPE_CAPTION_BAR).setFrame(new Rect(0, 0, 100, 300)); + mState.getSource(ITYPE_CAPTION_BAR).setVisible(true); + + Rect visibleInsets = mState.calculateVisibleInsets( + new Rect(0, 0, 100, 400), SOFT_INPUT_ADJUST_NOTHING); + assertEquals(new Rect(0, 300, 0, 0), visibleInsets); + } + } + + @Test + public void testCalculateInsets_captionBarOffset() throws Exception { + try (InsetsModeSession session = + new InsetsModeSession(ViewRootImpl.NEW_INSETS_MODE_FULL)) { + mState.getSource(ITYPE_CAPTION_BAR).setFrame(new Rect(0, 0, 100, 300)); + mState.getSource(ITYPE_CAPTION_BAR).setVisible(true); + + Rect visibleInsets = mState.calculateVisibleInsets( + new Rect(0, 0, 150, 400), SOFT_INPUT_ADJUST_NOTHING); + assertEquals(new Rect(0, 300, 0, 0), visibleInsets); + } + } + @Test public void testStripForDispatch() { mState.getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 100, 100));