diff --git a/core/java/android/view/ImeInsetsSourceConsumer.java b/core/java/android/view/ImeInsetsSourceConsumer.java index f06ba3b422df9..e89be476a9f0c 100644 --- a/core/java/android/view/ImeInsetsSourceConsumer.java +++ b/core/java/android/view/ImeInsetsSourceConsumer.java @@ -173,6 +173,7 @@ public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer { ImeTracker.PHASE_CLIENT_INSETS_CONSUMER_NOTIFY_HIDDEN); getImm().notifyImeHidden(mController.getHost().getWindowToken(), statsToken); + mIsRequestedVisibleAwaitingControl = false; Trace.asyncTraceEnd(TRACE_TAG_VIEW, "IC.hideRequestFromApi", 0); } diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java index 829321e3652bc..8e8e28a65eb4d 100644 --- a/core/java/android/view/InsetsController.java +++ b/core/java/android/view/InsetsController.java @@ -552,7 +552,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation this.useInsetsAnimationThread = useInsetsAnimationThread; } - final @InsetsType int types; + @InsetsType int types; final WindowInsetsAnimationControlListener listener; final long durationMs; final Interpolator interpolator; @@ -1006,19 +1006,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation } // Handle pending request ready in case there was one set. if (fromIme && mPendingImeControlRequest != null) { - PendingControlRequest pendingRequest = mPendingImeControlRequest; - mPendingImeControlRequest = null; - mHandler.removeCallbacks(mPendingControlTimeout); - - // We are about to playing the default animation. Passing a null frame indicates the - // controlled types should be animated regardless of the frame. - controlAnimationUnchecked( - pendingRequest.types, pendingRequest.cancellationSignal, - pendingRequest.listener, null /* frame */, - true /* fromIme */, pendingRequest.durationMs, pendingRequest.interpolator, - pendingRequest.animationType, - pendingRequest.layoutInsetsDuringAnimation, - pendingRequest.useInsetsAnimationThread, statsToken); + handlePendingControlRequest(statsToken); return; } @@ -1061,6 +1049,27 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation applyAnimation(typesReady, true /* show */, fromIme, statsToken); } + /** + * Handle the {@link #mPendingImeControlRequest} when + * - The IME insets is ready to show. + * - The IME insets has being requested invisible. + */ + private void handlePendingControlRequest(@Nullable ImeTracker.Token statsToken) { + PendingControlRequest pendingRequest = mPendingImeControlRequest; + mPendingImeControlRequest = null; + mHandler.removeCallbacks(mPendingControlTimeout); + + // We are about to playing the default animation. Passing a null frame indicates the + // controlled types should be animated regardless of the frame. + controlAnimationUnchecked( + pendingRequest.types, pendingRequest.cancellationSignal, + pendingRequest.listener, null /* frame */, + true /* fromIme */, pendingRequest.durationMs, pendingRequest.interpolator, + pendingRequest.animationType, + pendingRequest.layoutInsetsDuringAnimation, + pendingRequest.useInsetsAnimationThread, statsToken); + } + @Override public void hide(@InsetsType int types) { ImeTracker.Token statsToken = null; @@ -1084,6 +1093,8 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation Trace.asyncTraceBegin(TRACE_TAG_VIEW, "IC.hideRequestFromApi", 0); } int typesReady = 0; + boolean hasImeRequestedHidden = false; + final boolean hadPendingImeControlRequest = mPendingImeControlRequest != null; for (int type = FIRST; type <= LAST; type = type << 1) { if ((types & type) == 0) { continue; @@ -1091,6 +1102,22 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation @AnimationType final int animationType = getAnimationType(type); final boolean requestedVisible = (type & mRequestedVisibleTypes) != 0; final boolean isImeAnimation = type == ime(); + if (mPendingImeControlRequest != null && !requestedVisible) { + // Remove the hide insets type from the pending show request. + mPendingImeControlRequest.types &= ~type; + if (mPendingImeControlRequest.types == 0) { + abortPendingImeControlRequest(); + } + } + if (isImeAnimation && !requestedVisible && animationType == ANIMATION_TYPE_NONE) { + hasImeRequestedHidden = true; + // Ensure to request hide IME in case there is any pending requested visible + // being applied from setControl when receiving the insets control. + if (hadPendingImeControlRequest + || getImeSourceConsumer().isRequestedVisibleAwaitingControl()) { + getImeSourceConsumer().requestHide(fromIme, statsToken); + } + } if (!requestedVisible && animationType == ANIMATION_TYPE_NONE || animationType == ANIMATION_TYPE_HIDE) { // no-op: already hidden or animating out (because window visibility is @@ -1106,6 +1133,12 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation } typesReady |= type; } + if (hasImeRequestedHidden && mPendingImeControlRequest != null) { + // Handle the pending show request for other insets types since the IME insets has being + // requested hidden. + handlePendingControlRequest(statsToken); + getImeSourceConsumer().removeSurface(); + } applyAnimation(typesReady, false /* show */, fromIme, statsToken); } diff --git a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeOpenCloseStressTest.java b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeOpenCloseStressTest.java index a6131ce45c8bc..e3481849abdc2 100644 --- a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeOpenCloseStressTest.java +++ b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeOpenCloseStressTest.java @@ -16,6 +16,8 @@ package com.android.inputmethod.stresstest; +import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED; + import static com.android.inputmethod.stresstest.ImeStressTestUtil.INPUT_METHOD_MANAGER_HIDE_ON_CREATE; import static com.android.inputmethod.stresstest.ImeStressTestUtil.INPUT_METHOD_MANAGER_SHOW_ON_CREATE; import static com.android.inputmethod.stresstest.ImeStressTestUtil.REQUEST_FOCUS_ON_CREATE; @@ -209,8 +211,23 @@ public final class ImeOpenCloseStressTest { verifyShowBehavior(activity); } + /** + * Test IME hidden by calling show and hide IME consecutively with + * {@link android.view.inputmethod.InputMethodManager} APIs in + * {@link android.app.Activity#onCreate}. + * + *

Note for developers: Use {@link WindowManager.LayoutParams#SOFT_INPUT_STATE_UNCHANGED} + * window flag to avoid some softInputMode visibility flags may take presence over + * {@link android.view.inputmethod.InputMethodManager} APIs (e.g. use showSoftInput to show + * IME in {@link android.app.Activity#onCreate} but being hidden by + * {@link WindowManager.LayoutParams#SOFT_INPUT_STATE_ALWAYS_HIDDEN} window flag after the + * activity window focused).

+ */ @Test public void testShowHideWithInputMethodManager_onCreate() { + if (mSoftInputFlags != SOFT_INPUT_STATE_UNCHANGED) { + return; + } // Show and hide with InputMethodManager at onCreate() Intent intent = createIntent( @@ -222,10 +239,7 @@ public final class ImeOpenCloseStressTest { INPUT_METHOD_MANAGER_HIDE_ON_CREATE)); TestActivity activity = TestActivity.start(intent); - // TODO: The Ime is expected to show first and then hide. But show or hide - // with InputMethodManager at onCreate() would always fail because the window - // has not gained focus, so the actual behavior will be the same as auto-show. - // verifyHideBehavior(activity); + verifyHideBehavior(activity); } @Test @@ -352,8 +366,7 @@ public final class ImeOpenCloseStressTest { // Wait until IMMS / IMS handles messages. SystemClock.sleep(1000); mInstrumentation.waitForIdleSync(); - // TODO(b/248456059): Ime should be hidden but is shown. - // verifyHideBehavior(activity); + verifyHideBehavior(activity); mInstrumentation.runOnMainSync(activity::showImeWithWindowInsetsController); verifyShowBehavior(activity); @@ -420,11 +433,25 @@ public final class ImeOpenCloseStressTest { verifyShowBehaviorNotRequestFocus(activity); } + /** + * Test IME hidden by calling show and hide IME consecutively with + * {@link android.view.WindowInsetsController} APIs in {@link android.app.Activity#onCreate}. + * + *

Note for developers: Use {@link WindowManager.LayoutParams#SOFT_INPUT_STATE_UNCHANGED} + * window flag to avoid some softInputMode visibility flags may take presence over + * {@link android.view.WindowInsetsController} APIs (e.g. use showSoftInput to show + * IME in {@link android.app.Activity#onCreate} but being hidden by + * {@link WindowManager.LayoutParams#SOFT_INPUT_STATE_ALWAYS_HIDDEN} window flag after the + * activity window focused).

+ */ @Test public void testHideWithWindowInsetsController_onCreate_requestFocus() { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) { return; } + if (mSoftInputFlags != SOFT_INPUT_STATE_UNCHANGED) { + return; + } // Show and hide with InputMethodManager at onCreate() Intent intent = createIntent( @@ -436,8 +463,7 @@ public final class ImeOpenCloseStressTest { WINDOW_INSETS_CONTROLLER_HIDE_ON_CREATE)); TestActivity activity = TestActivity.start(intent); - // TODO(b/248456059): Ime should be hidden but is shown. - //verifyHideBehavior(activity); + verifyHideBehavior(activity); } @Test