summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/inputmethodservice/IInputMethodSessionWrapper.java10
-rw-r--r--core/java/android/inputmethodservice/InputMethodService.java12
-rw-r--r--core/java/android/view/ImeInsetsSourceConsumer.java49
-rw-r--r--core/java/android/view/inputmethod/InputMethodManager.java25
-rw-r--r--core/java/android/view/inputmethod/InputMethodSession.java7
-rw-r--r--core/java/android/view/inputmethod/InputMethodSessionWrapper.java9
-rw-r--r--core/java/com/android/internal/view/IInputMethodSession.aidl2
-rw-r--r--core/proto/android/view/imeinsetssourceconsumer.proto2
-rw-r--r--tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeOpenCloseStressTest.java124
9 files changed, 177 insertions, 63 deletions
diff --git a/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java b/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java
index eccbb403b306..9a7ccc64efcd 100644
--- a/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java
+++ b/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java
@@ -53,7 +53,6 @@ class IInputMethodSessionWrapper extends IInputMethodSession.Stub
private static final int DO_APP_PRIVATE_COMMAND = 100;
private static final int DO_FINISH_SESSION = 110;
private static final int DO_VIEW_CLICKED = 115;
- private static final int DO_NOTIFY_IME_HIDDEN = 120;
private static final int DO_REMOVE_IME_SURFACE = 130;
private static final int DO_FINISH_INPUT = 140;
private static final int DO_INVALIDATE_INPUT = 150;
@@ -133,10 +132,6 @@ class IInputMethodSessionWrapper extends IInputMethodSession.Stub
mInputMethodSession.viewClicked(msg.arg1 == 1);
return;
}
- case DO_NOTIFY_IME_HIDDEN: {
- mInputMethodSession.notifyImeHidden();
- return;
- }
case DO_REMOVE_IME_SURFACE: {
mInputMethodSession.removeImeSurface();
return;
@@ -198,11 +193,6 @@ class IInputMethodSessionWrapper extends IInputMethodSession.Stub
}
@Override
- public void notifyImeHidden() {
- mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_NOTIFY_IME_HIDDEN));
- }
-
- @Override
public void removeImeSurface() {
mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_REMOVE_IME_SURFACE));
}
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 4fdd53425328..a6ed42348af6 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -1058,10 +1058,6 @@ public class InputMethodService extends AbstractInputMethodService {
return viewRoot == null ? null : viewRoot.getInputToken();
}
- private void notifyImeHidden() {
- requestHideSelf(0);
- }
-
private void scheduleImeSurfaceRemoval() {
if (mShowInputRequested || mWindowVisible || mWindow == null
|| mImeSurfaceScheduledForRemoval) {
@@ -1225,14 +1221,6 @@ public class InputMethodService extends AbstractInputMethodService {
}
/**
- * Notify IME that window is hidden.
- * @hide
- */
- public final void notifyImeHidden() {
- InputMethodService.this.notifyImeHidden();
- }
-
- /**
* Notify IME that surface can be now removed.
* @hide
*/
diff --git a/core/java/android/view/ImeInsetsSourceConsumer.java b/core/java/android/view/ImeInsetsSourceConsumer.java
index d609fb8eb234..4fdea3b006dc 100644
--- a/core/java/android/view/ImeInsetsSourceConsumer.java
+++ b/core/java/android/view/ImeInsetsSourceConsumer.java
@@ -18,12 +18,13 @@ package android.view;
import static android.os.Trace.TRACE_TAG_VIEW;
import static android.view.ImeInsetsSourceConsumerProto.INSETS_SOURCE_CONSUMER;
+import static android.view.ImeInsetsSourceConsumerProto.IS_HIDE_ANIMATION_RUNNING;
import static android.view.ImeInsetsSourceConsumerProto.IS_REQUESTED_VISIBLE_AWAITING_CONTROL;
+import static android.view.ImeInsetsSourceConsumerProto.IS_SHOW_REQUESTED_DURING_HIDE_ANIMATION;
import static android.view.InsetsController.AnimationType;
import static android.view.InsetsState.ITYPE_IME;
import android.annotation.Nullable;
-import android.inputmethodservice.InputMethodService;
import android.os.IBinder;
import android.os.Trace;
import android.util.proto.ProtoOutputStream;
@@ -44,6 +45,16 @@ public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer {
*/
private boolean mIsRequestedVisibleAwaitingControl;
+ private boolean mIsHideAnimationRunning;
+
+ /**
+ * Tracks whether {@link WindowInsetsController#show(int)} or
+ * {@link InputMethodManager#showSoftInput(View, int)} is called during IME hide animation.
+ * If it was called, we should not call {@link InputMethodManager#notifyImeHidden(IBinder)},
+ * because the IME is being shown.
+ */
+ private boolean mIsShowRequestedDuringHideAnimation;
+
public ImeInsetsSourceConsumer(
InsetsState state, Supplier<Transaction> transactionSupplier,
InsetsController controller) {
@@ -64,6 +75,12 @@ public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer {
}
@Override
+ public void show(boolean fromIme) {
+ super.show(fromIme);
+ onShowRequested();
+ }
+
+ @Override
public void hide() {
super.hide();
mIsRequestedVisibleAwaitingControl = false;
@@ -74,10 +91,20 @@ public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer {
hide();
if (animationFinished) {
- // remove IME surface as IME has finished hide animation.
- notifyHidden();
- removeSurface();
+ // Remove IME surface as IME has finished hide animation, if there is no pending
+ // show request.
+ if (!mIsShowRequestedDuringHideAnimation) {
+ notifyHidden();
+ removeSurface();
+ }
}
+ // This method is called
+ // (1) before the hide animation starts.
+ // (2) after the hide animation ends.
+ // (3) if the IME is not controllable (animationFinished == true in this case).
+ // We should reset mIsShowRequestedDuringHideAnimation in all cases.
+ mIsHideAnimationRunning = !animationFinished;
+ mIsShowRequestedDuringHideAnimation = false;
}
/**
@@ -104,7 +131,8 @@ public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer {
}
/**
- * Notify {@link InputMethodService} that IME window is hidden.
+ * Notify {@link com.android.server.inputmethod.InputMethodManagerService} that
+ * IME insets are hidden.
*/
@Override
void notifyHidden() {
@@ -157,9 +185,20 @@ public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer {
final long token = proto.start(fieldId);
super.dumpDebug(proto, INSETS_SOURCE_CONSUMER);
proto.write(IS_REQUESTED_VISIBLE_AWAITING_CONTROL, mIsRequestedVisibleAwaitingControl);
+ proto.write(IS_HIDE_ANIMATION_RUNNING, mIsHideAnimationRunning);
+ proto.write(IS_SHOW_REQUESTED_DURING_HIDE_ANIMATION, mIsShowRequestedDuringHideAnimation);
proto.end(token);
}
+ /**
+ * Called when {@link #show} or {@link InputMethodManager#showSoftInput(View, int)} is called.
+ */
+ public void onShowRequested() {
+ if (mIsHideAnimationRunning) {
+ mIsShowRequestedDuringHideAnimation = true;
+ }
+ }
+
private InputMethodManager getImm() {
return mController.getHost().getInputMethodManager();
}
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 84f13930e03a..850256871b15 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -543,6 +543,7 @@ public final class InputMethodManager {
static final int MSG_BIND_ACCESSIBILITY_SERVICE = 11;
static final int MSG_UNBIND_ACCESSIBILITY_SERVICE = 12;
static final int MSG_UPDATE_VIRTUAL_DISPLAY_TO_SCREEN_MATRIX = 30;
+ static final int MSG_ON_SHOW_REQUESTED = 31;
private static boolean isAutofillUIShowing(View servedView) {
AutofillManager afm = servedView.getContext().getSystemService(AutofillManager.class);
@@ -1117,6 +1118,14 @@ public final class InputMethodManager {
}
return;
}
+ case MSG_ON_SHOW_REQUESTED: {
+ synchronized (mH) {
+ if (mImeInsetsConsumer != null) {
+ mImeInsetsConsumer.onShowRequested();
+ }
+ }
+ return;
+ }
}
}
}
@@ -1834,6 +1843,9 @@ public final class InputMethodManager {
return false;
}
+ // Makes sure to call ImeInsetsSourceConsumer#onShowRequested on the UI thread.
+ // TODO(b/229426865): call WindowInsetsController#show instead.
+ mH.executeOrSendMessage(Message.obtain(mH, MSG_ON_SHOW_REQUESTED));
try {
Log.d(TAG, "showSoftInput() view=" + view + " flags=" + flags + " reason="
+ InputMethodDebug.softInputDisplayReasonToString(reason));
@@ -1869,6 +1881,9 @@ public final class InputMethodManager {
Log.w(TAG, "No current root view, ignoring showSoftInputUnchecked()");
return;
}
+ // Makes sure to call ImeInsetsSourceConsumer#onShowRequested on the UI thread.
+ // TODO(b/229426865): call WindowInsetsController#show instead.
+ mH.executeOrSendMessage(Message.obtain(mH, MSG_ON_SHOW_REQUESTED));
mService.showSoftInput(
mClient,
mCurRootView.getView().getWindowToken(),
@@ -2521,7 +2536,7 @@ public final class InputMethodManager {
}
/**
- * Notify IME directly that it is no longer visible.
+ * Notify IMMS that IME insets are no longer visible.
*
* @param windowToken the window from which this request originates. If this doesn't match the
* currently served view, the request is ignored.
@@ -2533,7 +2548,13 @@ public final class InputMethodManager {
synchronized (mH) {
if (mCurrentInputMethodSession != null && mCurRootView != null
&& mCurRootView.getWindowToken() == windowToken) {
- mCurrentInputMethodSession.notifyImeHidden();
+ try {
+ mService.hideSoftInput(mClient, windowToken, 0 /* flags */,
+ null /* resultReceiver */,
+ SoftInputShowHideReason.HIDE_SOFT_INPUT);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
}
}
}
diff --git a/core/java/android/view/inputmethod/InputMethodSession.java b/core/java/android/view/inputmethod/InputMethodSession.java
index a178ee8ee866..28c44507e43c 100644
--- a/core/java/android/view/inputmethod/InputMethodSession.java
+++ b/core/java/android/view/inputmethod/InputMethodSession.java
@@ -195,13 +195,6 @@ public interface InputMethodSession {
public void updateCursorAnchorInfo(CursorAnchorInfo cursorAnchorInfo);
/**
- * Notifies {@link android.inputmethodservice.InputMethodService} that IME has been
- * hidden from user.
- * @hide
- */
- public void notifyImeHidden();
-
- /**
* Notify IME directly to remove surface as it is no longer visible.
* @hide
*/
diff --git a/core/java/android/view/inputmethod/InputMethodSessionWrapper.java b/core/java/android/view/inputmethod/InputMethodSessionWrapper.java
index a1995202485f..ee22b6508549 100644
--- a/core/java/android/view/inputmethod/InputMethodSessionWrapper.java
+++ b/core/java/android/view/inputmethod/InputMethodSessionWrapper.java
@@ -106,15 +106,6 @@ final class InputMethodSessionWrapper {
}
@AnyThread
- void notifyImeHidden() {
- try {
- mSession.notifyImeHidden();
- } catch (RemoteException e) {
- Log.w(TAG, "IME died", e);
- }
- }
-
- @AnyThread
void viewClicked(boolean focusChanged) {
try {
mSession.viewClicked(focusChanged);
diff --git a/core/java/com/android/internal/view/IInputMethodSession.aidl b/core/java/com/android/internal/view/IInputMethodSession.aidl
index b9eb997b851b..d505c1995def 100644
--- a/core/java/com/android/internal/view/IInputMethodSession.aidl
+++ b/core/java/com/android/internal/view/IInputMethodSession.aidl
@@ -50,8 +50,6 @@ oneway interface IInputMethodSession {
void updateCursorAnchorInfo(in CursorAnchorInfo cursorAnchorInfo);
- void notifyImeHidden();
-
void removeImeSurface();
void finishInput();
diff --git a/core/proto/android/view/imeinsetssourceconsumer.proto b/core/proto/android/view/imeinsetssourceconsumer.proto
index b1ed365309c4..6e007fad5f8d 100644
--- a/core/proto/android/view/imeinsetssourceconsumer.proto
+++ b/core/proto/android/view/imeinsetssourceconsumer.proto
@@ -29,4 +29,6 @@ message ImeInsetsSourceConsumerProto {
optional InsetsSourceConsumerProto insets_source_consumer = 1;
reserved 2; // focused_editor = 2
optional bool is_requested_visible_awaiting_control = 3;
+ optional bool is_hide_animation_running = 4;
+ optional bool is_show_requested_during_hide_animation = 5;
} \ No newline at end of file
diff --git a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeOpenCloseStressTest.java b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeOpenCloseStressTest.java
index 1c957d4d78cf..8419276f4406 100644
--- a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeOpenCloseStressTest.java
+++ b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeOpenCloseStressTest.java
@@ -30,6 +30,7 @@ import android.os.Bundle;
import android.os.SystemClock;
import android.platform.test.annotations.RootPermissionTest;
import android.platform.test.rule.UnlockScreenRule;
+import android.util.Log;
import android.view.WindowInsets;
import android.view.WindowInsetsAnimation;
import android.view.inputmethod.InputMethodManager;
@@ -40,16 +41,19 @@ import androidx.annotation.Nullable;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.platform.app.InstrumentationRegistry;
+import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.ArrayList;
import java.util.List;
@RootPermissionTest
@RunWith(AndroidJUnit4.class)
public final class ImeOpenCloseStressTest {
+ private static final String TAG = "ImeOpenCloseStressTest";
private static final int NUM_TEST_ITERATIONS = 10;
@Rule
@@ -58,32 +62,103 @@ public final class ImeOpenCloseStressTest {
@Rule
public ScreenCaptureRule mScreenCaptureRule =
new ScreenCaptureRule("/sdcard/InputMethodStressTest");
+ private Instrumentation mInstrumentation;
+
+ @Before
+ public void setUp() {
+ mInstrumentation = InstrumentationRegistry.getInstrumentation();
+ }
+
+ @Test
+ public void testShowHide_waitingVisibilityChange() {
+ TestActivity activity = TestActivity.start();
+ EditText editText = activity.getEditText();
+ waitOnMainUntil("activity should gain focus", editText::hasWindowFocus);
+ for (int i = 0; i < NUM_TEST_ITERATIONS; i++) {
+ String msgPrefix = "Iteration #" + i + " ";
+ Log.i(TAG, msgPrefix + "start");
+ mInstrumentation.runOnMainSync(activity::showIme);
+ waitOnMainUntil(msgPrefix + "IME should be visible", () -> isImeShown(editText));
+ mInstrumentation.runOnMainSync(activity::hideIme);
+ waitOnMainUntil(msgPrefix + "IME should be hidden", () -> !isImeShown(editText));
+ }
+ }
@Test
- public void test() {
- Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
- Intent intent = new Intent()
- .setAction(Intent.ACTION_MAIN)
- .setClass(instrumentation.getContext(), TestActivity.class)
- .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
- .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
- TestActivity activity = (TestActivity) instrumentation.startActivitySync(intent);
+ public void testShowHide_waitingAnimationEnd() {
+ TestActivity activity = TestActivity.start();
+ activity.enableAnimationMonitoring();
EditText editText = activity.getEditText();
waitOnMainUntil("activity should gain focus", editText::hasWindowFocus);
for (int i = 0; i < NUM_TEST_ITERATIONS; i++) {
String msgPrefix = "Iteration #" + i + " ";
- instrumentation.runOnMainSync(activity::showIme);
+ Log.i(TAG, msgPrefix + "start");
+ mInstrumentation.runOnMainSync(activity::showIme);
waitOnMainUntil(msgPrefix + "IME should be visible",
() -> !activity.isAnimating() && isImeShown(editText));
- instrumentation.runOnMainSync(activity::hideIme);
+ mInstrumentation.runOnMainSync(activity::hideIme);
waitOnMainUntil(msgPrefix + "IME should be hidden",
() -> !activity.isAnimating() && !isImeShown(editText));
- // b/b/221483132, wait until IMS and IMMS handles IMM#notifyImeHidden.
- // There is no good signal, so we just wait a second.
- SystemClock.sleep(1000);
}
}
+ @Test
+ public void testShowHide_intervalAfterHide() {
+ // Regression test for b/221483132
+ TestActivity activity = TestActivity.start();
+ EditText editText = activity.getEditText();
+ // Intervals = 10, 20, 30, ..., 100, 150, 200, ...
+ List<Integer> intervals = new ArrayList<>();
+ for (int i = 10; i < 100; i += 10) intervals.add(i);
+ for (int i = 100; i < 1000; i += 50) intervals.add(i);
+ waitOnMainUntil("activity should gain focus", editText::hasWindowFocus);
+ for (int intervalMillis : intervals) {
+ String msgPrefix = "Interval = " + intervalMillis + " ";
+ Log.i(TAG, msgPrefix + " start");
+ mInstrumentation.runOnMainSync(activity::hideIme);
+ SystemClock.sleep(intervalMillis);
+ mInstrumentation.runOnMainSync(activity::showIme);
+ waitOnMainUntil(msgPrefix + "IME should be visible",
+ () -> isImeShown(editText));
+ }
+ }
+
+ @Test
+ public void testShowHideInSameFrame() {
+ TestActivity activity = TestActivity.start();
+ activity.enableAnimationMonitoring();
+ EditText editText = activity.getEditText();
+ waitOnMainUntil("activity should gain focus", editText::hasWindowFocus);
+
+ // hidden -> show -> hide
+ mInstrumentation.runOnMainSync(() -> {
+ Log.i(TAG, "Calling showIme() and hideIme()");
+ activity.showIme();
+ activity.hideIme();
+ });
+ // Wait until IMMS / IMS handles messages.
+ SystemClock.sleep(1000);
+ mInstrumentation.waitForIdleSync();
+ waitOnMainUntil("IME should be invisible after show/hide", () -> !isImeShown(editText));
+
+ mInstrumentation.runOnMainSync(activity::showIme);
+ waitOnMainUntil("IME should be visible",
+ () -> !activity.isAnimating() && isImeShown(editText));
+ mInstrumentation.waitForIdleSync();
+
+ // shown -> hide -> show
+ mInstrumentation.runOnMainSync(() -> {
+ Log.i(TAG, "Calling hideIme() and showIme()");
+ activity.hideIme();
+ activity.showIme();
+ });
+ // Wait until IMMS / IMS handles messages.
+ SystemClock.sleep(1000);
+ mInstrumentation.waitForIdleSync();
+ waitOnMainUntil("IME should be visible after hide/show",
+ () -> !activity.isAnimating() && isImeShown(editText));
+ }
+
public static class TestActivity extends Activity {
private EditText mEditText;
@@ -111,6 +186,15 @@ public final class ImeOpenCloseStressTest {
}
};
+ public static TestActivity start() {
+ Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+ Intent intent = new Intent()
+ .setAction(Intent.ACTION_MAIN)
+ .setClass(instrumentation.getContext(), TestActivity.class)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ return (TestActivity) instrumentation.startActivitySync(intent);
+ }
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
@@ -120,9 +204,6 @@ public final class ImeOpenCloseStressTest {
mEditText = new EditText(this);
rootView.addView(mEditText, new LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT));
setContentView(rootView);
- // Enable WindowInsetsAnimation.
- getWindow().setDecorFitsSystemWindows(false);
- mEditText.setWindowInsetsAnimationCallback(mWindowInsetsAnimationCallback);
}
public EditText getEditText() {
@@ -130,16 +211,27 @@ public final class ImeOpenCloseStressTest {
}
public void showIme() {
+ Log.i(TAG, "TestActivity.showIme");
mEditText.requestFocus();
InputMethodManager imm = getSystemService(InputMethodManager.class);
imm.showSoftInput(mEditText, 0);
}
public void hideIme() {
+ Log.i(TAG, "TestActivity.hideIme");
InputMethodManager imm = getSystemService(InputMethodManager.class);
imm.hideSoftInputFromWindow(mEditText.getWindowToken(), 0);
}
+ public void enableAnimationMonitoring() {
+ // Enable WindowInsetsAnimation.
+ // Note that this has a side effect of disabling InsetsAnimationThreadControlRunner.
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+ getWindow().setDecorFitsSystemWindows(false);
+ mEditText.setWindowInsetsAnimationCallback(mWindowInsetsAnimationCallback);
+ });
+ }
+
public boolean isAnimating() {
return mIsAnimating;
}