diff options
8 files changed, 248 insertions, 46 deletions
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java index 7ad97a6d393e..1bf027e77379 100644 --- a/core/java/android/view/InsetsController.java +++ b/core/java/android/view/InsetsController.java @@ -133,6 +133,10 @@ public class InsetsController implements WindowInsetsController { } void onFrameChanged(Rect frame) { + if (mFrame.equals(frame)) { + return; + } + mViewRoot.notifyInsetsChanged(); mFrame.set(frame); } @@ -257,11 +261,21 @@ public class InsetsController implements WindowInsetsController { private void controlWindowInsetsAnimation(@InsetType int types, WindowInsetsAnimationControlListener listener, boolean fromIme) { + // If the frame of our window doesn't span the entire display, the control API makes very + // little sense, as we don't deal with negative insets. So just cancel immediately. + if (!mState.getDisplayFrame().equals(mFrame)) { + listener.onCancelled(); + return; + } + controlAnimationUnchecked(types, listener, mFrame, fromIme); + } + + private void controlAnimationUnchecked(@InsetType int types, + WindowInsetsAnimationControlListener listener, Rect frame, boolean fromIme) { if (types == 0) { // nothing to animate. return; } - // TODO: Check whether we already have a controller. final ArraySet<Integer> internalTypes = mState.toInternalType(types); final SparseArray<InsetsSourceConsumer> consumers = new SparseArray<>(); @@ -285,7 +299,7 @@ public class InsetsController implements WindowInsetsController { } final InsetsAnimationControlImpl controller = new InsetsAnimationControlImpl(consumers, - mFrame, mState, listener, typesReady, + frame, mState, listener, typesReady, () -> new SyncRtSurfaceTransactionApplier(mViewRoot.mView), this); mAnimationControls.add(controller); } @@ -436,6 +450,7 @@ public class InsetsController implements WindowInsetsController { // nothing to animate. return; } + WindowInsetsAnimationControlListener listener = new WindowInsetsAnimationControlListener() { @Override public void onReady(WindowInsetsAnimationController controller, int types) { @@ -479,7 +494,10 @@ public class InsetsController implements WindowInsetsController { // TODO: Instead of clearing this here, properly wire up // InsetsAnimationControlImpl.finish() to remove this from mAnimationControls. mAnimationControls.clear(); - controlWindowInsetsAnimation(types, listener, fromIme); + + // Show/hide animations always need to be relative to the display frame, in order that shown + // and hidden state insets are correct. + controlAnimationUnchecked(types, listener, mState.getDisplayFrame(), fromIme); } private void hideDirectly(@InsetType int types) { diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java index 4f809fe6d54a..c9273d8cc2a9 100644 --- a/core/java/android/view/InsetsState.java +++ b/core/java/android/view/InsetsState.java @@ -39,6 +39,7 @@ import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; +import java.util.Objects; /** * Holder for state of system windows that cause window insets for all other windows in the system. @@ -104,6 +105,11 @@ public class InsetsState implements Parcelable { private final ArrayMap<Integer, InsetsSource> mSources = new ArrayMap<>(); + /** + * The frame of the display these sources are relative to. + */ + private final Rect mDisplayFrame = new Rect(); + public InsetsState() { } @@ -209,6 +215,14 @@ public class InsetsState implements Parcelable { return mSources.computeIfAbsent(type, InsetsSource::new); } + public void setDisplayFrame(Rect frame) { + mDisplayFrame.set(frame); + } + + public Rect getDisplayFrame() { + return mDisplayFrame; + } + /** * Modifies the state of this class to exclude a certain type to make it ready for dispatching * to the client. @@ -224,6 +238,7 @@ public class InsetsState implements Parcelable { } public void set(InsetsState other, boolean copySources) { + mDisplayFrame.set(other.mDisplayFrame); mSources.clear(); if (copySources) { for (int i = 0; i < other.mSources.size(); i++) { @@ -323,6 +338,9 @@ public class InsetsState implements Parcelable { InsetsState state = (InsetsState) o; + if (!mDisplayFrame.equals(state.mDisplayFrame)) { + return false; + } if (mSources.size() != state.mSources.size()) { return false; } @@ -341,7 +359,7 @@ public class InsetsState implements Parcelable { @Override public int hashCode() { - return mSources.hashCode(); + return Objects.hash(mDisplayFrame, mSources); } public InsetsState(Parcel in) { @@ -355,9 +373,10 @@ public class InsetsState implements Parcelable { @Override public void writeToParcel(Parcel dest, int flags) { + dest.writeParcelable(mDisplayFrame, flags); dest.writeInt(mSources.size()); for (int i = 0; i < mSources.size(); i++) { - dest.writeParcelable(mSources.valueAt(i), 0 /* flags */); + dest.writeParcelable(mSources.valueAt(i), flags); } } @@ -374,6 +393,7 @@ public class InsetsState implements Parcelable { public void readFromParcel(Parcel in) { mSources.clear(); + mDisplayFrame.set(in.readParcelable(null /* loader */)); final int size = in.readInt(); for (int i = 0; i < size; i++) { final InsetsSource source = in.readParcelable(null /* loader */); diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java index 6dad6a22f7ea..3fc3773ff3da 100644 --- a/core/tests/coretests/src/android/view/InsetsControllerTest.java +++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java @@ -24,6 +24,11 @@ import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertNull; import static junit.framework.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; import android.content.Context; import android.graphics.Insets; @@ -95,6 +100,17 @@ public class InsetsControllerTest { } @Test + public void testFrameDoesntMatchDisplay() { + mController.onFrameChanged(new Rect(0, 0, 100, 100)); + mController.getState().setDisplayFrame(new Rect(0, 0, 200, 200)); + WindowInsetsAnimationControlListener controlListener = + mock(WindowInsetsAnimationControlListener.class); + mController.controlWindowInsetsAnimation(0, controlListener); + verify(controlListener).onCancelled(); + verify(controlListener, never()).onReady(any(), anyInt()); + } + + @Test public void testAnimationEndState() { InsetsSourceControl[] controls = prepareControls(); InsetsSourceControl navBar = controls[0]; diff --git a/core/tests/coretests/src/android/view/InsetsStateTest.java b/core/tests/coretests/src/android/view/InsetsStateTest.java index 03af67df13d0..bfe6e32b6261 100644 --- a/core/tests/coretests/src/android/view/InsetsStateTest.java +++ b/core/tests/coretests/src/android/view/InsetsStateTest.java @@ -37,6 +37,7 @@ import android.os.Parcel; import android.platform.test.annotations.Presubmit; import android.util.SparseIntArray; import android.view.WindowInsets.Type; +import android.view.test.InsetsModeSession; import androidx.test.filters.FlakyTest; import androidx.test.runner.AndroidJUnit4; @@ -53,49 +54,58 @@ public class InsetsStateTest { private InsetsState mState2 = new InsetsState(); @Test - public void testCalculateInsets() { - mState.getSource(TYPE_TOP_BAR).setFrame(new Rect(0, 0, 100, 100)); - mState.getSource(TYPE_TOP_BAR).setVisible(true); - mState.getSource(TYPE_IME).setFrame(new Rect(0, 200, 100, 300)); - mState.getSource(TYPE_IME).setVisible(true); - SparseIntArray typeSideMap = new SparseIntArray(); - WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), false, false, - DisplayCutout.NO_CUTOUT, null, null, typeSideMap); - assertEquals(Insets.of(0, 100, 0, 100), insets.getSystemWindowInsets()); - assertEquals(Insets.of(0, 100, 0, 100), insets.getInsets(Type.all())); - assertEquals(INSET_SIDE_TOP, typeSideMap.get(TYPE_TOP_BAR)); - assertEquals(INSET_SIDE_BOTTOM, typeSideMap.get(TYPE_IME)); - assertEquals(Insets.of(0, 100, 0, 0), insets.getInsets(Type.topBar())); - assertEquals(Insets.of(0, 0, 0, 100), insets.getInsets(Type.ime())); + public void testCalculateInsets() throws Exception { + try (final InsetsModeSession session = + new InsetsModeSession(ViewRootImpl.NEW_INSETS_MODE_FULL)) { + mState.getSource(TYPE_TOP_BAR).setFrame(new Rect(0, 0, 100, 100)); + mState.getSource(TYPE_TOP_BAR).setVisible(true); + mState.getSource(TYPE_IME).setFrame(new Rect(0, 200, 100, 300)); + mState.getSource(TYPE_IME).setVisible(true); + SparseIntArray typeSideMap = new SparseIntArray(); + WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), false, false, + DisplayCutout.NO_CUTOUT, null, null, typeSideMap); + assertEquals(Insets.of(0, 100, 0, 100), insets.getSystemWindowInsets()); + assertEquals(Insets.of(0, 100, 0, 100), insets.getInsets(Type.all())); + assertEquals(INSET_SIDE_TOP, typeSideMap.get(TYPE_TOP_BAR)); + assertEquals(INSET_SIDE_BOTTOM, typeSideMap.get(TYPE_IME)); + assertEquals(Insets.of(0, 100, 0, 0), insets.getInsets(Type.topBar())); + assertEquals(Insets.of(0, 0, 0, 100), insets.getInsets(Type.ime())); + } } @Test - public void testCalculateInsets_imeAndNav() { - mState.getSource(TYPE_NAVIGATION_BAR).setFrame(new Rect(0, 200, 100, 300)); - mState.getSource(TYPE_NAVIGATION_BAR).setVisible(true); - mState.getSource(TYPE_IME).setFrame(new Rect(0, 100, 100, 300)); - mState.getSource(TYPE_IME).setVisible(true); - WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), false, false, - DisplayCutout.NO_CUTOUT, null, null, null); - assertEquals(100, insets.getStableInsetBottom()); - assertEquals(Insets.of(0, 0, 0, 100), insets.getMaxInsets(Type.all())); - assertEquals(Insets.of(0, 0, 0, 200), insets.getSystemWindowInsets()); - assertEquals(Insets.of(0, 0, 0, 200), insets.getInsets(Type.all())); - assertEquals(Insets.of(0, 0, 0, 100), insets.getInsets(Type.sideBars())); - assertEquals(Insets.of(0, 0, 0, 200), insets.getInsets(Type.ime())); + public void testCalculateInsets_imeAndNav() throws Exception{ + try (final InsetsModeSession session = + new InsetsModeSession(ViewRootImpl.NEW_INSETS_MODE_FULL)) { + mState.getSource(TYPE_NAVIGATION_BAR).setFrame(new Rect(0, 200, 100, 300)); + mState.getSource(TYPE_NAVIGATION_BAR).setVisible(true); + mState.getSource(TYPE_IME).setFrame(new Rect(0, 100, 100, 300)); + mState.getSource(TYPE_IME).setVisible(true); + WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), false, false, + DisplayCutout.NO_CUTOUT, null, null, null); + assertEquals(100, insets.getStableInsetBottom()); + assertEquals(Insets.of(0, 0, 0, 100), insets.getMaxInsets(Type.systemBars())); + assertEquals(Insets.of(0, 0, 0, 200), insets.getSystemWindowInsets()); + assertEquals(Insets.of(0, 0, 0, 200), insets.getInsets(Type.all())); + assertEquals(Insets.of(0, 0, 0, 100), insets.getInsets(Type.sideBars())); + assertEquals(Insets.of(0, 0, 0, 200), insets.getInsets(Type.ime())); + } } @Test - public void testCalculateInsets_navRightStatusTop() { - mState.getSource(TYPE_TOP_BAR).setFrame(new Rect(0, 0, 100, 100)); - mState.getSource(TYPE_TOP_BAR).setVisible(true); - mState.getSource(TYPE_NAVIGATION_BAR).setFrame(new Rect(80, 0, 100, 300)); - mState.getSource(TYPE_NAVIGATION_BAR).setVisible(true); - WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), false, false, - DisplayCutout.NO_CUTOUT, null, null, null); - assertEquals(Insets.of(0, 100, 20, 0), insets.getSystemWindowInsets()); - assertEquals(Insets.of(0, 100, 0, 0), insets.getInsets(Type.topBar())); - assertEquals(Insets.of(0, 0, 20, 0), insets.getInsets(Type.sideBars())); + public void testCalculateInsets_navRightStatusTop() throws Exception { + try (final InsetsModeSession session = + new InsetsModeSession(ViewRootImpl.NEW_INSETS_MODE_FULL)) { + mState.getSource(TYPE_TOP_BAR).setFrame(new Rect(0, 0, 100, 100)); + mState.getSource(TYPE_TOP_BAR).setVisible(true); + mState.getSource(TYPE_NAVIGATION_BAR).setFrame(new Rect(80, 0, 100, 300)); + mState.getSource(TYPE_NAVIGATION_BAR).setVisible(true); + WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), false, false, + DisplayCutout.NO_CUTOUT, null, null, null); + assertEquals(Insets.of(0, 100, 20, 0), insets.getSystemWindowInsets()); + assertEquals(Insets.of(0, 100, 0, 0), insets.getInsets(Type.topBar())); + assertEquals(Insets.of(0, 0, 20, 0), insets.getInsets(Type.sideBars())); + } } @Test @@ -114,14 +124,14 @@ public class InsetsStateTest { public void testEquals_differentRect() { mState.getSource(TYPE_TOP_BAR).setFrame(new Rect(0, 0, 100, 100)); mState2.getSource(TYPE_TOP_BAR).setFrame(new Rect(0, 0, 10, 10)); - assertNotEquals(mState, mState2); + assertNotEqualsAndHashCode(); } @Test public void testEquals_differentSource() { mState.getSource(TYPE_TOP_BAR).setFrame(new Rect(0, 0, 100, 100)); mState2.getSource(TYPE_IME).setFrame(new Rect(0, 0, 100, 100)); - assertNotEquals(mState, mState2); + assertNotEqualsAndHashCode(); } @Test @@ -130,7 +140,7 @@ public class InsetsStateTest { mState.getSource(TYPE_IME).setFrame(new Rect(0, 0, 100, 100)); mState2.getSource(TYPE_IME).setFrame(new Rect(0, 0, 100, 100)); mState2.getSource(TYPE_TOP_BAR).setFrame(new Rect(0, 0, 100, 100)); - assertEquals(mState, mState2); + assertEqualsAndHashCode(); } @Test @@ -138,7 +148,21 @@ public class InsetsStateTest { mState.getSource(TYPE_IME).setFrame(new Rect(0, 0, 100, 100)); mState.getSource(TYPE_IME).setVisible(true); mState2.getSource(TYPE_IME).setFrame(new Rect(0, 0, 100, 100)); - assertNotEquals(mState, mState2); + assertNotEqualsAndHashCode(); + } + + @Test + public void testEquals_differentFrame() { + mState.setDisplayFrame(new Rect(0, 1, 2, 3)); + mState.setDisplayFrame(new Rect(4, 5, 6, 7)); + assertNotEqualsAndHashCode(); + } + + @Test + public void testEquals_sameFrame() { + mState.setDisplayFrame(new Rect(0, 1, 2, 3)); + mState2.setDisplayFrame(new Rect(0, 1, 2, 3)); + assertEqualsAndHashCode(); } @Test @@ -148,6 +172,7 @@ public class InsetsStateTest { mState.getSource(TYPE_TOP_BAR).setFrame(new Rect(0, 0, 100, 100)); Parcel p = Parcel.obtain(); mState.writeToParcel(p, 0 /* flags */); + p.setDataPosition(0); mState2.readFromParcel(p); p.recycle(); assertEquals(mState, mState2); @@ -161,4 +186,14 @@ public class InsetsStateTest { assertTrue(InsetsState.getDefaultVisibility(TYPE_SIDE_BAR_3)); assertFalse(InsetsState.getDefaultVisibility(TYPE_IME)); } + + private void assertEqualsAndHashCode() { + assertEquals(mState, mState2); + assertEquals(mState.hashCode(), mState2.hashCode()); + } + + private void assertNotEqualsAndHashCode() { + assertNotEquals(mState, mState2); + assertNotEquals(mState.hashCode(), mState2.hashCode()); + } } diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 7a8fd49361b6..a67c72a79b78 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -38,6 +38,7 @@ import static android.view.Surface.ROTATION_180; import static android.view.Surface.ROTATION_270; import static android.view.Surface.ROTATION_90; import static android.view.View.GONE; +import static android.view.ViewRootImpl.NEW_INSETS_MODE_NONE; import static android.view.WindowManager.DOCKED_BOTTOM; import static android.view.WindowManager.DOCKED_INVALID; import static android.view.WindowManager.DOCKED_TOP; @@ -172,6 +173,7 @@ import android.view.SurfaceControl; import android.view.SurfaceControl.Transaction; import android.view.SurfaceSession; import android.view.View; +import android.view.ViewRootImpl; import android.view.WindowManager; import android.view.WindowManagerPolicyConstants.PointerEventListener; @@ -3251,6 +3253,36 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo mInputMethodTargetWaitingAnim = targetWaitingAnim; assignWindowLayers(false /* setLayoutNeeded */); mInsetsStateController.onImeTargetChanged(target); + updateImeParent(); + } + + private void updateImeParent() { + if (ViewRootImpl.sNewInsetsMode == NEW_INSETS_MODE_NONE) { + return; + } + final SurfaceControl newParent = computeImeParent(); + if (newParent != null) { + mPendingTransaction.reparent(mImeWindowsContainers.mSurfaceControl, newParent); + scheduleAnimation(); + } + } + + /** + * Computes the window the IME should be attached to. + */ + @VisibleForTesting + SurfaceControl computeImeParent() { + + // Attach it to app if the target is part of an app and such app is covering the entire + // screen. If it's not covering the entire screen the IME might extend beyond the apps + // bounds. + if (mInputMethodTarget != null && mInputMethodTarget.mAppToken != null && + mInputMethodTarget.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) { + return mInputMethodTarget.mAppToken.getSurfaceControl(); + } + + // Otherwise, we just attach it to the display. + return mWindowingLayer; } boolean getNeedsMenu(WindowState top, WindowManagerPolicy.WindowState bottom) { @@ -4885,6 +4917,11 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo .reparent(mWindowingLayer, sc).reparent(mOverlayLayer, sc); } + @VisibleForTesting + SurfaceControl getWindowingLayer() { + return mWindowingLayer; + } + /** * Create a portal window handle for input. This window transports any touch to the display * indicated by {@link InputWindowHandle#portalToDisplayId} if the touch hits this window. diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java index afae9c4ac228..a1b52f424fee 100644 --- a/services/core/java/com/android/server/wm/InsetsStateController.java +++ b/services/core/java/com/android/server/wm/InsetsStateController.java @@ -112,6 +112,7 @@ class InsetsStateController { * Called when a layout pass has occurred. */ void onPostLayout() { + mState.setDisplayFrame(mDisplayContent.getBounds()); for (int i = mControllers.size() - 1; i>= 0; i--) { mControllers.valueAt(i).onPostLayout(); } diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java index 3826fac22b05..a62bc713db40 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java @@ -51,17 +51,22 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.eq; import android.annotation.SuppressLint; +import android.app.WindowConfiguration; import android.content.res.Configuration; import android.graphics.Rect; import android.os.SystemClock; import android.platform.test.annotations.Presubmit; import android.util.DisplayMetrics; import android.view.DisplayCutout; +import android.view.DisplayInfo; import android.view.Gravity; import android.view.MotionEvent; import android.view.Surface; +import android.view.ViewRootImpl; +import android.view.test.InsetsModeSession; import androidx.test.filters.FlakyTest; import androidx.test.filters.SmallTest; @@ -628,6 +633,39 @@ public class DisplayContentTests extends WindowTestsBase { eq(activityRecord), anyBoolean(), eq(dc.getDisplayId())); } + @Test + public void testComputeImeParent_app() throws Exception { + try (final InsetsModeSession session = + new InsetsModeSession(ViewRootImpl.NEW_INSETS_MODE_IME)) { + final DisplayContent dc = createNewDisplay(); + dc.mInputMethodTarget = createWindow(null, TYPE_BASE_APPLICATION, "app"); + assertEquals(dc.mInputMethodTarget.mAppToken.getSurfaceControl(), + dc.computeImeParent()); + } + } + + @Test + public void testComputeImeParent_app_notFullscreen() throws Exception { + try (final InsetsModeSession session = + new InsetsModeSession(ViewRootImpl.NEW_INSETS_MODE_IME)) { + final DisplayContent dc = createNewDisplay(); + dc.mInputMethodTarget = createWindow(null, TYPE_STATUS_BAR, "app"); + dc.mInputMethodTarget.setWindowingMode( + WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY); + assertEquals(dc.getWindowingLayer(), dc.computeImeParent()); + } + } + + @Test + public void testComputeImeParent_noApp() throws Exception { + try (final InsetsModeSession session = + new InsetsModeSession(ViewRootImpl.NEW_INSETS_MODE_IME)) { + final DisplayContent dc = createNewDisplay(); + dc.mInputMethodTarget = createWindow(null, TYPE_STATUS_BAR, "statusBar"); + assertEquals(dc.getWindowingLayer(), dc.computeImeParent()); + } + } + private boolean isOptionsPanelAtRight(int displayId) { return (mWm.getPreferredOptionsPanelGravity(displayId) & Gravity.RIGHT) == Gravity.RIGHT; } diff --git a/tests/utils/testutils/java/android/view/test/InsetsModeSession.java b/tests/utils/testutils/java/android/view/test/InsetsModeSession.java new file mode 100644 index 000000000000..c83dfa41d260 --- /dev/null +++ b/tests/utils/testutils/java/android/view/test/InsetsModeSession.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2019 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 android.view.test; + +import android.view.ViewRootImpl; + +/** + * Session to set insets mode for {@link ViewRootImpl#sNewInsetsMode}. + */ +public class InsetsModeSession implements AutoCloseable { + + private int mOldMode; + + public InsetsModeSession(int flag) { + mOldMode = ViewRootImpl.sNewInsetsMode; + ViewRootImpl.sNewInsetsMode = flag; + } + + @Override + public void close() throws Exception { + ViewRootImpl.sNewInsetsMode = mOldMode; + } +} |