diff --git a/core/java/android/inputmethodservice/IInputMethodWrapper.java b/core/java/android/inputmethodservice/IInputMethodWrapper.java index bc407504a20db..562f40bc7513a 100644 --- a/core/java/android/inputmethodservice/IInputMethodWrapper.java +++ b/core/java/android/inputmethodservice/IInputMethodWrapper.java @@ -166,8 +166,9 @@ class IInputMethodWrapper extends IInputMethod.Stub final SomeArgs args = (SomeArgs) msg.obj; final int missingMethods = msg.arg1; final boolean restarting = msg.arg2 != 0; - final IInputContext inputContext = (IInputContext) args.arg1; - final EditorInfo info = (EditorInfo) args.arg2; + final IBinder startInputToken = (IBinder) args.arg1; + final IInputContext inputContext = (IInputContext) args.arg2; + final EditorInfo info = (EditorInfo) args.arg3; final InputConnection ic = inputContext != null ? new InputConnectionWrapper(mTarget, inputContext, missingMethods) : null; info.makeCompatible(mTargetSdkVersion); @@ -176,6 +177,8 @@ class IInputMethodWrapper extends IInputMethod.Stub } else { inputMethod.startInput(ic, info); } + inputMethod.dispatchStartInputWithToken(ic, info, true /* initial */, + startInputToken); args.recycle(); return; } @@ -255,11 +258,11 @@ class IInputMethodWrapper extends IInputMethod.Stub } @Override - public void startInput(IInputContext inputContext, + public void startInput(IBinder startInputToken, IInputContext inputContext, @InputConnectionInspector.MissingMethodFlags final int missingMethods, EditorInfo attribute, boolean restarting) { - mCaller.executeOrSendMessage(mCaller.obtainMessageIIOO(DO_START_INPUT, - missingMethods, restarting ? 1 : 0, inputContext, attribute)); + mCaller.executeOrSendMessage(mCaller.obtainMessageIIOOO(DO_START_INPUT, + missingMethods, restarting ? 1 : 0, startInputToken, inputContext, attribute)); } @Override diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java index 5ae1fd0571226..7a20943e2a4bb 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -24,6 +24,7 @@ import android.annotation.DrawableRes; import android.annotation.IntDef; import android.annotation.MainThread; import android.annotation.NonNull; +import android.annotation.Nullable; import android.app.ActivityManager; import android.app.Dialog; import android.content.Context; @@ -291,7 +292,20 @@ public class InputMethodService extends AbstractInputMethodService { boolean mCandidatesViewStarted; InputConnection mStartedInputConnection; EditorInfo mInputEditorInfo; - + + /** + * A token to keep tracking the last IPC that triggered + * {@link #doStartInput(InputConnection, EditorInfo, boolean)}. If + * {@link #doStartInput(InputConnection, EditorInfo, boolean)} was not caused by IPCs from + * {@link com.android.server.InputMethodManagerService}, this needs to remain unchanged. + * + *
Some IPCs to {@link com.android.server.InputMethodManagerService} require this token to + * disentangle event flows for various purposes such as better window animation and providing + * fine-grained debugging information.
+ */ + @Nullable + private IBinder mStartInputToken; + int mShowInputFlags; boolean mShowInputRequested; boolean mLastShowInputRequested; @@ -415,6 +429,23 @@ public class InputMethodService extends AbstractInputMethodService { doStartInput(ic, attribute, true); } + /** + * {@inheritDoc} + * @hide + */ + @Override + public void dispatchStartInputWithToken(@Nullable InputConnection inputConnection, + @NonNull EditorInfo editorInfo, boolean restarting, + @NonNull IBinder startInputToken) { + mStartInputToken = startInputToken; + + // This needs to be dispatched to interface methods rather than doStartInput(). + // Otherwise IME developers who have overridden those interface methods will lose + // notifications. + super.dispatchStartInputWithToken(inputConnection, editorInfo, restarting, + startInputToken); + } + /** * Handle a request by the system to hide the soft input area. */ @@ -454,8 +485,8 @@ public class InputMethodService extends AbstractInputMethodService { clearInsetOfPreviousIme(); // If user uses hard keyboard, IME button should always be shown. boolean showing = isInputViewShown(); - mImm.setImeWindowStatus(mToken, IME_ACTIVE | (showing ? IME_VISIBLE : 0), - mBackDisposition); + mImm.setImeWindowStatus(mToken, mStartInputToken, + IME_ACTIVE | (showing ? IME_VISIBLE : 0), mBackDisposition); if (resultReceiver != null) { resultReceiver.send(wasVis != isInputViewShown() ? InputMethodManager.RESULT_SHOWN @@ -926,8 +957,8 @@ public class InputMethodService extends AbstractInputMethodService { } // If user uses hard keyboard, IME button should always be shown. boolean showing = onEvaluateInputViewShown(); - mImm.setImeWindowStatus(mToken, IME_ACTIVE | (showing ? IME_VISIBLE : 0), - mBackDisposition); + mImm.setImeWindowStatus(mToken, mStartInputToken, + IME_ACTIVE | (showing ? IME_VISIBLE : 0), mBackDisposition); } } @@ -1653,7 +1684,8 @@ public class InputMethodService extends AbstractInputMethodService { final int nextImeWindowStatus = IME_ACTIVE | (isInputViewShown() ? IME_VISIBLE : 0); if (previousImeWindowStatus != nextImeWindowStatus) { - mImm.setImeWindowStatus(mToken, nextImeWindowStatus, mBackDisposition); + mImm.setImeWindowStatus(mToken, mStartInputToken, nextImeWindowStatus, + mBackDisposition); } if ((previousImeWindowStatus & IME_ACTIVE) == 0) { if (DEBUG) Log.v(TAG, "showWindow: showing!"); @@ -1678,7 +1710,7 @@ public class InputMethodService extends AbstractInputMethodService { } private void doHideWindow() { - mImm.setImeWindowStatus(mToken, 0, mBackDisposition); + mImm.setImeWindowStatus(mToken, mStartInputToken, 0, mBackDisposition); hideWindow(); } @@ -2643,7 +2675,8 @@ public class InputMethodService extends AbstractInputMethodService { p.println(" mInputStarted=" + mInputStarted + " mInputViewStarted=" + mInputViewStarted + " mCandidatesViewStarted=" + mCandidatesViewStarted); - + p.println(" mStartInputToken=" + mStartInputToken); + if (mInputEditorInfo != null) { p.println(" mInputEditorInfo:"); mInputEditorInfo.dump(p, " "); diff --git a/core/java/android/view/inputmethod/InputMethod.java b/core/java/android/view/inputmethod/InputMethod.java index 5ab3d34a8b3d5..0922422c5125e 100644 --- a/core/java/android/view/inputmethod/InputMethod.java +++ b/core/java/android/view/inputmethod/InputMethod.java @@ -16,6 +16,8 @@ package android.view.inputmethod; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; import android.inputmethodservice.InputMethodService; @@ -147,6 +149,44 @@ public interface InputMethod { */ public void restartInput(InputConnection inputConnection, EditorInfo attribute); + /** + * This method is called when {@code {@link #startInput(InputConnection, EditorInfo)} or + * {@code {@link #restartInput(InputConnection, EditorInfo)} needs to be dispatched. + * + *Note: This method is hidden because the {@code startInputToken} that this method is + * dealing with is one of internal details, which should not be exposed to the IME developers. + * If you override this method, you are responsible for not breaking existing IMEs that expect + * {@link #startInput(InputConnection, EditorInfo)} to be still called back.
+ * + * @param inputConnection optional specific input connection for communicating with the text + * box; if {@code null}, you should use the generic bound input + * connection + * @param editorInfo information about the text box (typically, an EditText) that requests input + * @param restarting {@code false} if this corresponds to + * {@link #startInput(InputConnection, EditorInfo)}. Otherwise this + * corresponds to {@link #restartInput(InputConnection, EditorInfo)}. + * @param startInputToken a token that identifies a logical session that starts with this method + * call. Some internal IPCs such as {@link + * InputMethodManager#setImeWindowStatus(IBinder, IBinder, int, int)} + * require this token to work, and you have to keep the token alive until + * the next {@link #startInput(InputConnection, EditorInfo, IBinder)} as + * long as your implementation of {@link InputMethod} relies on such + * IPCs + * @see #startInput(InputConnection, EditorInfo) + * @see #restartInput(InputConnection, EditorInfo) + * @see EditorInfo + * @hide + */ + default void dispatchStartInputWithToken(@Nullable InputConnection inputConnection, + @NonNull EditorInfo editorInfo, boolean restarting, + @NonNull IBinder startInputToken) { + if (restarting) { + restartInput(inputConnection, editorInfo); + } else { + startInput(inputConnection, editorInfo); + } + } + /** * Create a new {@link InputMethodSession} that can be handed to client * applications for interacting with the input method. You can later diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index 1f76d28fab05e..79b0420a77e33 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -739,9 +739,10 @@ public final class InputMethodManager { } /** @hide */ - public void setImeWindowStatus(IBinder imeToken, int vis, int backDisposition) { + public void setImeWindowStatus(IBinder imeToken, IBinder startInputToken, int vis, + int backDisposition) { try { - mService.setImeWindowStatus(imeToken, vis, backDisposition); + mService.setImeWindowStatus(imeToken, startInputToken, vis, backDisposition); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/com/android/internal/os/HandlerCaller.java b/core/java/com/android/internal/os/HandlerCaller.java index c26fc3a0a7135..ae7c5f230e223 100644 --- a/core/java/com/android/internal/os/HandlerCaller.java +++ b/core/java/com/android/internal/os/HandlerCaller.java @@ -173,6 +173,16 @@ public class HandlerCaller { return mH.obtainMessage(what, arg1, arg2, args); } + public Message obtainMessageIIOOOO(int what, int arg1, int arg2, Object arg3, Object arg4, + Object arg5, Object arg6) { + SomeArgs args = SomeArgs.obtain(); + args.arg1 = arg3; + args.arg2 = arg4; + args.arg3 = arg5; + args.arg4 = arg6; + return mH.obtainMessage(what, arg1, arg2, args); + } + public Message obtainMessageOO(int what, Object arg1, Object arg2) { SomeArgs args = SomeArgs.obtain(); args.arg1 = arg1; diff --git a/core/java/com/android/internal/view/IInputMethod.aidl b/core/java/com/android/internal/view/IInputMethod.aidl index 9870612dfac51..5b29f4cb9fb4b 100644 --- a/core/java/com/android/internal/view/IInputMethod.aidl +++ b/core/java/com/android/internal/view/IInputMethod.aidl @@ -38,8 +38,8 @@ oneway interface IInputMethod { void unbindInput(); - void startInput(in IInputContext inputContext, int missingMethods, in EditorInfo attribute, - boolean restarting); + void startInput(in IBinder startInputToken, in IInputContext inputContext, int missingMethods, + in EditorInfo attribute, boolean restarting); void createSession(in InputChannel channel, IInputSessionCallback callback); diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl index 437764562b837..b9798075ad271 100644 --- a/core/java/com/android/internal/view/IInputMethodManager.aidl +++ b/core/java/com/android/internal/view/IInputMethodManager.aidl @@ -70,7 +70,8 @@ interface IInputMethodManager { void hideMySoftInput(in IBinder token, int flags); void showMySoftInput(in IBinder token, int flags); void updateStatusIcon(in IBinder token, String packageName, int iconId); - void setImeWindowStatus(in IBinder token, int vis, int backDisposition); + void setImeWindowStatus(in IBinder token, in IBinder startInputToken, int vis, + int backDisposition); void registerSuggestionSpansForNotification(in SuggestionSpan[] spans); boolean notifySuggestionPicked(in SuggestionSpan span, String originalString, int index); InputMethodSubtype getCurrentInputMethodSubtype(); diff --git a/services/core/java/com/android/server/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java index b7a1f8b34277a..5935600d7513a 100644 --- a/services/core/java/com/android/server/InputMethodManagerService.java +++ b/services/core/java/com/android/server/InputMethodManagerService.java @@ -124,6 +124,7 @@ import android.view.WindowManager; import android.view.WindowManagerInternal; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputBinding; +import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputConnectionInspector; import android.view.inputmethod.InputMethod; import android.view.inputmethod.InputMethodInfo; @@ -152,6 +153,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; +import java.util.WeakHashMap; /** * This class provides a system service that manages input methods. @@ -506,6 +508,36 @@ public class InputMethodManagerService extends IInputMethodManager.Stub @HardKeyboardBehavior private final int mHardKeyboardBehavior; + /** + * Internal state snapshot when {@link #MSG_START_INPUT} message is about to be posted to the + * internal message queue. Any subsequent state change inside {@link InputMethodManagerService} + * will not affect those tasks that are already posted. + * + *Posting {@link #MSG_START_INPUT} message basically means that + * {@link InputMethodService#doStartInput(InputConnection, EditorInfo, boolean)} will be called + * back in the current IME process shortly, which will also affect what the current IME starts + * receiving from {@link InputMethodService#getCurrentInputConnection()}. In other words, this + * snapshot will be taken every time when {@link InputMethodManagerService} is initiating a new + * logical input session between the client application and the current IME.
+ * + *Be careful to not keep strong references to this object forever, which can prevent + * {@link StartInputInfo#mImeToken} and {@link StartInputInfo#mTargetWindow} from being GC-ed. + *
+ */ + private static class StartInputInfo { + @NonNull + final IBinder mImeToken; + @Nullable + final IBinder mTargetWindow; + + StartInputInfo(@NonNull IBinder imeToken, @Nullable IBinder targetWindow) { + mImeToken = imeToken; + mTargetWindow = targetWindow; + } + } + + private WeakHashMap