From 737c79151cb6fc005bbce145f91606af69065539 Mon Sep 17 00:00:00 2001 From: Ming-Shin Lu Date: Thu, 29 Dec 2022 06:00:20 +0000 Subject: [PATCH 1/2] Fix WIC#hide(ime()) no-op after WIC#show(ime()) in Activity#onCreate In InsetsController#hide, ensure to request hide IME with calling ImeInsetsSurceConsumer#requestHide, in case the pending visible request will triggger show after setControl. Also, we modified PendingControlRequest to be able to remove from the show insets type when it has being requested invisible by InsetsController#hide(type), if the removed type contains the IME then we handle the pending show control request for other types since it is no longer needed to wait the IME ready to show. Also, make sure set mIsRequestedVisibleAwaitingControl as false when ImeInsetsSourceConsumer#notifyHidden to avoid the above pending visible request happens. Bug: 248456059 Test: atest CtsInputMethodTestCases Test: atest InputMethodStressTest:ImeOpenCloseStressTest Change-Id: Id3aebca3348888826bc5d3c6694e94cddb80b4d4 --- .../android/view/ImeInsetsSourceConsumer.java | 1 + core/java/android/view/InsetsController.java | 61 ++++++++++++++----- 2 files changed, 48 insertions(+), 14 deletions(-) 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); } From c49c7b4514536daed26d5c49987c5bd083ecf2a7 Mon Sep 17 00:00:00 2001 From: Ming-Shin Lu Date: Thu, 29 Dec 2022 06:06:51 +0000 Subject: [PATCH 2/2] Remove TODO in InputMethodStressTest Since CL[1] has fixed WIC#hide(ime()) not work in onCreate, now we can remove the comment of verifyHideBehavior back for following test methods in ImeOpenCloseStressTest to ensure the test coverage: - testShowHideWithInputMethodManager_onCreate - testHideWithWindowInsetsController_onCraate_requestFocus - testShowHideWithInputMethodManager_inSameFrame Also, in case calling show/hide IME consecutively when in Activity#onCreate that comes before the window focus changed and the final IME visiblity may be affected by SOFT_INPUT_STATE_.* visiblity flag after gaining the window focus, ensure setting SOFT_INPUT_STATE_UNCHANGED flag in following tests: - testHideWithWindowInsetsController_onCreate_requestFocus - testShowHideWithInputMethodManager_onCreate [1]: Id3aebca3348888826bc5d3c6694e94cddb80b4d4 Fix: 248456059 Test: atest InputMethodStressTest:ImeOpenCloseStressTest Change-Id: Ib7baf28036f01201176d2a5bba4ebde319ca8f1a --- .../stresstest/ImeOpenCloseStressTest.java | 42 +++++++++++++++---- 1 file changed, 34 insertions(+), 8 deletions(-) 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