From a4a6960b62ec5505f0b83273b5c3a6757e52923c Mon Sep 17 00:00:00 2001 From: Yohei Yukawa Date: Fri, 18 Feb 2022 12:05:15 -0800 Subject: [PATCH] Fix NPE when IMM#invalidateInput() is called too early This is a follow up CL to my previous CL [1], which introduced InputMethodManager#invalidateInput(View). One thing I overlooked is that apps may call that API even before InputMethodManager#mCurrentInputMethodSession, which results in a NullPointerException. With this CL, such a case will be gracefully fallen back into InputMethodManager#restartInput(View) like other cases when IMM#invalidateInput() is not available. [1]: I3161755779080f98bcef0e47dd0c5247d8a3a256 daa6695c2ee8f7a8a9d724e4169ced4d28edc54d Fix: 213350732 Test: atest CtsInputMethodTestCases:InputMethodStartInputLifecycleTest Change-Id: I67458494b3069498a1395e3278f6a975e821e610 --- .../android/view/inputmethod/InputMethodManager.java | 11 +++++++++-- .../inputmethod/RemoteInputConnectionImpl.java | 5 ++--- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index 2359d8d67f672..27174630bfa33 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -2031,14 +2031,20 @@ public final class InputMethodManager { * @param inputConnection the connection to be invalidated. * @param textSnapshot {@link TextSnapshot} to be used to update {@link EditorInfo}. * @param sessionId the session ID to be sent. + * @return {@code true} if the operation is done. {@code false} if the caller needs to fall back + * to {@link InputMethodManager#restartInput(View)}. * @hide */ - public void doInvalidateInput(@NonNull RemoteInputConnectionImpl inputConnection, + public boolean doInvalidateInput(@NonNull RemoteInputConnectionImpl inputConnection, @NonNull TextSnapshot textSnapshot, int sessionId) { synchronized (mH) { if (mServedInputConnection != inputConnection || mCurrentTextBoxAttribute == null) { // OK to ignore because the calling InputConnection is already abandoned. - return; + return true; + } + if (mCurrentInputMethodSession == null) { + // IME is not yet bound to the client. Need to fall back to the restartInput(). + return false; } final EditorInfo editorInfo = mCurrentTextBoxAttribute.createCopyInternal(); editorInfo.initialSelStart = mCursorSelStart = textSnapshot.getSelectionStart(); @@ -2051,6 +2057,7 @@ public final class InputMethodManager { sessionId); forAccessibilitySessions(wrapper -> wrapper.invalidateInput(editorInfo, mServedInputConnection, sessionId)); + return true; } } diff --git a/core/java/com/android/internal/inputmethod/RemoteInputConnectionImpl.java b/core/java/com/android/internal/inputmethod/RemoteInputConnectionImpl.java index d1942ac26aff3..780de0eb4da24 100644 --- a/core/java/com/android/internal/inputmethod/RemoteInputConnectionImpl.java +++ b/core/java/com/android/internal/inputmethod/RemoteInputConnectionImpl.java @@ -282,9 +282,8 @@ public final class RemoteInputConnectionImpl extends IInputContext.Stub { if (!alwaysTrueEndBatchEditDetected) { final TextSnapshot textSnapshot = ic.takeSnapshot(); - if (textSnapshot != null) { - mParentInputMethodManager.doInvalidateInput(this, textSnapshot, - nextSessionId); + if (textSnapshot != null && mParentInputMethodManager.doInvalidateInput( + this, textSnapshot, nextSessionId)) { return; } }