diff options
12 files changed, 294 insertions, 56 deletions
diff --git a/core/java/android/view/InsetsAnimationControlImpl.java b/core/java/android/view/InsetsAnimationControlImpl.java index 583651dee379..dd88e3c34d30 100644 --- a/core/java/android/view/InsetsAnimationControlImpl.java +++ b/core/java/android/view/InsetsAnimationControlImpl.java @@ -33,6 +33,7 @@ import android.util.SparseSetArray; import android.view.InsetsState.InsetSide; import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams; import android.view.WindowInsets.Type.InsetType; +import android.view.WindowManager.LayoutParams; import com.android.internal.annotations.VisibleForTesting; @@ -165,7 +166,8 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll @Nullable @InsetSide SparseIntArray typeSideMap) { return state.calculateInsets(frame, false /* isScreenRound */, false /* alwaysConsumerNavBar */, null /* displayCutout */, - null /* legacyContentInsets */, null /* legacyStableInsets */, typeSideMap) + null /* legacyContentInsets */, null /* legacyStableInsets */, + LayoutParams.SOFT_INPUT_ADJUST_RESIZE /* legacySoftInputMode*/, typeSideMap) .getInsets(mTypes); } diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java index 7ad97a6d393e..258600019e71 100644 --- a/core/java/android/view/InsetsController.java +++ b/core/java/android/view/InsetsController.java @@ -112,6 +112,8 @@ public class InsetsController implements WindowInsetsController { private int mPendingTypesToShow; + private int mLastLegacySoftInputMode; + public InsetsController(ViewRootImpl viewRoot) { mViewRoot = viewRoot; mAnimCallback = () -> { @@ -126,13 +128,17 @@ public class InsetsController implements WindowInsetsController { } WindowInsets insets = state.calculateInsets(mFrame, mLastInsets.isRound(), mLastInsets.shouldAlwaysConsumeNavBar(), mLastInsets.getDisplayCutout(), - mLastLegacyContentInsets, mLastLegacyStableInsets, + mLastLegacyContentInsets, mLastLegacyStableInsets, mLastLegacySoftInputMode, null /* typeSideMap */); mViewRoot.mView.dispatchWindowInsetsAnimationProgress(insets); }; } void onFrameChanged(Rect frame) { + if (mFrame.equals(frame)) { + return; + } + mViewRoot.notifyInsetsChanged(); mFrame.set(frame); } @@ -160,11 +166,12 @@ public class InsetsController implements WindowInsetsController { @VisibleForTesting public WindowInsets calculateInsets(boolean isScreenRound, boolean alwaysConsumeNavBar, DisplayCutout cutout, Rect legacyContentInsets, - Rect legacyStableInsets) { + Rect legacyStableInsets, int legacySoftInputMode) { mLastLegacyContentInsets.set(legacyContentInsets); mLastLegacyStableInsets.set(legacyStableInsets); + mLastLegacySoftInputMode = legacySoftInputMode; mLastInsets = mState.calculateInsets(mFrame, isScreenRound, alwaysConsumeNavBar, cutout, - legacyContentInsets, legacyStableInsets, + legacyContentInsets, legacyStableInsets, legacySoftInputMode, null /* typeSideMap */); return mLastInsets; } @@ -257,11 +264,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 +302,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 +453,7 @@ public class InsetsController implements WindowInsetsController { // nothing to animate. return; } + WindowInsetsAnimationControlListener listener = new WindowInsetsAnimationControlListener() { @Override public void onReady(WindowInsetsAnimationController controller, int types) { @@ -479,7 +497,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..69f86aa5b37e 100644 --- a/core/java/android/view/InsetsState.java +++ b/core/java/android/view/InsetsState.java @@ -19,6 +19,7 @@ package android.view; import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL; import static android.view.ViewRootImpl.NEW_INSETS_MODE_IME; import static android.view.ViewRootImpl.NEW_INSETS_MODE_NONE; +import static android.view.WindowInsets.Type.IME; import static android.view.WindowInsets.Type.SIZE; import static android.view.WindowInsets.Type.indexOf; @@ -34,11 +35,13 @@ import android.util.SparseArray; import android.util.SparseIntArray; import android.view.WindowInsets.Type; import android.view.WindowInsets.Type.InsetType; +import android.view.WindowManager.LayoutParams; 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 +107,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() { } @@ -124,7 +132,7 @@ public class InsetsState implements Parcelable { public WindowInsets calculateInsets(Rect frame, boolean isScreenRound, boolean alwaysConsumeNavBar, DisplayCutout cutout, @Nullable Rect legacyContentInsets, @Nullable Rect legacyStableInsets, - @Nullable @InsetSide SparseIntArray typeSideMap) { + int legacySoftInputMode, @Nullable @InsetSide SparseIntArray typeSideMap) { Insets[] typeInsetsMap = new Insets[Type.SIZE]; Insets[] typeMaxInsetsMap = new Insets[Type.SIZE]; boolean[] typeVisibilityMap = new boolean[SIZE]; @@ -140,8 +148,12 @@ public class InsetsState implements Parcelable { if (source == null) { continue; } - if (ViewRootImpl.sNewInsetsMode != NEW_INSETS_MODE_FULL - && (type == TYPE_TOP_BAR || type == TYPE_NAVIGATION_BAR)) { + + boolean skipSystemBars = ViewRootImpl.sNewInsetsMode != NEW_INSETS_MODE_FULL + && (type == TYPE_TOP_BAR || type == TYPE_NAVIGATION_BAR); + boolean skipIme = source.getType() == TYPE_IME + && (legacySoftInputMode & LayoutParams.SOFT_INPUT_ADJUST_RESIZE) == 0; + if (skipSystemBars || skipIme) { typeVisibilityMap[indexOf(toPublicType(type))] = source.isVisible(); continue; } @@ -209,6 +221,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 +244,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 +344,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 +365,7 @@ public class InsetsState implements Parcelable { @Override public int hashCode() { - return mSources.hashCode(); + return Objects.hash(mDisplayFrame, mSources); } public InsetsState(Parcel in) { @@ -355,9 +379,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 +399,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/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 156972f6ecbb..1a782ee5d3dd 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -1887,7 +1887,7 @@ public final class ViewRootImpl implements ViewParent, mLastWindowInsets = mInsetsController.calculateInsets( mContext.getResources().getConfiguration().isScreenRound(), mAttachInfo.mAlwaysConsumeNavBar, displayCutout, - contentInsets, stableInsets); + contentInsets, stableInsets, mWindowAttributes.softInputMode); } else { mLastWindowInsets = new WindowInsets(contentInsets, stableInsets, mContext.getResources().getConfiguration().isScreenRound(), diff --git a/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java b/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java index da81d176de8a..80b1f9cb35ae 100644 --- a/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java +++ b/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java @@ -19,6 +19,7 @@ package android.view; import static android.view.ImeInsetsSourceConsumer.areEditorsSimilar; import static android.view.InsetsState.TYPE_IME; +import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; @@ -73,7 +74,7 @@ public class ImeInsetsSourceConsumerTest { false, new DisplayCutout( Insets.of(10, 10, 10, 10), rect, rect, rect, rect), - rect, rect); + rect, rect, SOFT_INPUT_ADJUST_RESIZE); mImeConsumer = new ImeInsetsSourceConsumer( new InsetsState(), Transaction::new, mController); }); diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java index 6dad6a22f7ea..731d5644504b 100644 --- a/core/tests/coretests/src/android/view/InsetsControllerTest.java +++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java @@ -20,10 +20,16 @@ import static android.view.InsetsState.TYPE_IME; import static android.view.InsetsState.TYPE_NAVIGATION_BAR; import static android.view.InsetsState.TYPE_TOP_BAR; +import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; 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; @@ -73,7 +79,7 @@ public class InsetsControllerTest { false, new DisplayCutout( Insets.of(10, 10, 10, 10), rect, rect, rect, rect), - rect, rect); + rect, rect, SOFT_INPUT_ADJUST_RESIZE); }); InstrumentationRegistry.getInstrumentation().waitForIdleSync(); } @@ -95,6 +101,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..bd036b01c0cf 100644 --- a/core/tests/coretests/src/android/view/InsetsStateTest.java +++ b/core/tests/coretests/src/android/view/InsetsStateTest.java @@ -25,6 +25,8 @@ import static android.view.InsetsState.TYPE_SIDE_BAR_2; import static android.view.InsetsState.TYPE_SIDE_BAR_3; import static android.view.InsetsState.TYPE_TOP_BAR; +import static android.view.WindowInsets.Type.ime; +import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; @@ -37,6 +39,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 +56,70 @@ 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, SOFT_INPUT_ADJUST_RESIZE, 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, SOFT_INPUT_ADJUST_RESIZE, 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() { + 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, 0, 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 + public void testCalculateInsets_imeIgnoredWithoutAdjustResize() { 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); + mState.getSource(TYPE_IME).setFrame(new Rect(0, 200, 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(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())); + DisplayCutout.NO_CUTOUT, null, null, 0, null); + assertEquals(0, insets.getSystemWindowInsetBottom()); + assertTrue(insets.isVisible(ime())); } @Test @@ -106,7 +130,7 @@ public class InsetsStateTest { mState.getSource(TYPE_IME).setVisible(true); mState.removeSource(TYPE_IME); WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), false, false, - DisplayCutout.NO_CUTOUT, null, null, null); + DisplayCutout.NO_CUTOUT, null, null, SOFT_INPUT_ADJUST_RESIZE, null); assertEquals(0, insets.getSystemWindowInsetBottom()); } @@ -114,14 +138,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 +154,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 +162,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 +186,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 +200,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 928b57cf050b..429bce050142 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/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index bbf115f17969..f98ca3d8889d 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -27,6 +27,7 @@ import static android.content.res.Configuration.UI_MODE_TYPE_CAR; import static android.content.res.Configuration.UI_MODE_TYPE_MASK; import static android.view.InsetsState.TYPE_TOP_BAR; import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; +import static android.view.ViewRootImpl.NEW_INSETS_MODE_NONE; import static android.view.WindowManager.INPUT_CONSUMER_NAVIGATION; import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW; import static android.view.WindowManager.LayoutParams.FIRST_SYSTEM_WINDOW; @@ -134,6 +135,7 @@ import android.view.MotionEvent; import android.view.PointerIcon; import android.view.Surface; import android.view.View; +import android.view.ViewRootImpl; import android.view.WindowManager; import android.view.WindowManager.LayoutParams; import android.view.WindowManagerGlobal; @@ -1900,7 +1902,10 @@ public class DisplayPolicy { if (win.isVoiceInteraction()) { cf.set(displayFrames.mVoiceContent); } else { - if (adjust != SOFT_INPUT_ADJUST_RESIZE) { + // IME Insets are handled on the client for ADJUST_RESIZE in the new + // insets world + if (ViewRootImpl.sNewInsetsMode != NEW_INSETS_MODE_NONE + || adjust != SOFT_INPUT_ADJUST_RESIZE) { cf.set(displayFrames.mDock); } else { cf.set(displayFrames.mContent); @@ -1991,7 +1996,11 @@ public class DisplayPolicy { of.set(displayFrames.mRestricted); df.set(displayFrames.mRestricted); pf.set(displayFrames.mRestricted); - if (adjust != SOFT_INPUT_ADJUST_RESIZE) { + + // IME Insets are handled on the client for ADJUST_RESIZE in the new insets + // world + if (ViewRootImpl.sNewInsetsMode != NEW_INSETS_MODE_NONE + || adjust != SOFT_INPUT_ADJUST_RESIZE) { cf.set(displayFrames.mDock); } else { cf.set(displayFrames.mContent); 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; + } +} |