From bb0e2f753b4b6e37ab0411499c25661abfbf6553 Mon Sep 17 00:00:00 2001 From: Tarandeep Singh Date: Wed, 15 Jan 2020 13:58:29 -0800 Subject: [PATCH] Pipe windowToken of window requesting IME It takes time from when IME is requested to the time when IME is ready to be shown. When its ready to be shown, we need to make sure that window that requested IME is still the IME target in DisplayContent. The only realistic way of knowing originating window is passing windowToken from IMM API. Bug: 111084606 Test: CtsInputMethodTestCases Change-Id: Ia49e23dd077d264a58d28a7b8acffde54b7db187 --- .../IInputMethodWrapper.java | 13 +++-- .../InputMethodService.java | 48 ++++++++++++++++++- .../android/view/inputmethod/InputMethod.java | 36 +++++++++++++- .../view/inputmethod/InputMethodManager.java | 6 ++- .../IInputMethodPrivilegedOperations.aidl | 2 +- .../InputMethodPrivilegedOperations.java | 9 ++-- .../android/internal/view/IInputMethod.aidl | 2 +- .../internal/view/IInputMethodManager.aidl | 2 +- .../InputMethodManagerService.java | 45 ++++++++++------- .../MultiClientInputMethodManagerService.java | 3 +- 10 files changed, 133 insertions(+), 33 deletions(-) diff --git a/core/java/android/inputmethodservice/IInputMethodWrapper.java b/core/java/android/inputmethodservice/IInputMethodWrapper.java index 4d5fabbae9615..2a441de067c6d 100644 --- a/core/java/android/inputmethodservice/IInputMethodWrapper.java +++ b/core/java/android/inputmethodservice/IInputMethodWrapper.java @@ -221,7 +221,9 @@ class IInputMethodWrapper extends IInputMethod.Stub inputMethod.revokeSession((InputMethodSession)msg.obj); return; case DO_SHOW_SOFT_INPUT: - inputMethod.showSoftInput(msg.arg1, (ResultReceiver)msg.obj); + SomeArgs args = (SomeArgs)msg.obj; + inputMethod.showSoftInputWithToken( + msg.arg1, (ResultReceiver) args.arg2, (IBinder) args.arg1); return; case DO_HIDE_SOFT_INPUT: inputMethod.hideSoftInput(msg.arg1, (ResultReceiver)msg.obj); @@ -230,10 +232,11 @@ class IInputMethodWrapper extends IInputMethod.Stub inputMethod.changeInputMethodSubtype((InputMethodSubtype)msg.obj); return; case DO_CREATE_INLINE_SUGGESTIONS_REQUEST: - SomeArgs args = (SomeArgs) msg.obj; + args = (SomeArgs) msg.obj; inputMethod.onCreateInlineSuggestionsRequest((ComponentName) args.arg1, (AutofillId) args.arg2, (IInlineSuggestionsRequestCallback) args.arg3); return; + } Log.w(TAG, "Unhandled message code: " + msg.what); } @@ -371,9 +374,9 @@ class IInputMethodWrapper extends IInputMethod.Stub @BinderThread @Override - public void showSoftInput(int flags, ResultReceiver resultReceiver) { - mCaller.executeOrSendMessage(mCaller.obtainMessageIO(DO_SHOW_SOFT_INPUT, - flags, resultReceiver)); + public void showSoftInput(IBinder showInputToken, int flags, ResultReceiver resultReceiver) { + mCaller.executeOrSendMessage(mCaller.obtainMessageIOO(DO_SHOW_SOFT_INPUT, + flags, showInputToken, resultReceiver)); } @BinderThread diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java index 8e52ee944c1cf..81a0d629380ac 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -46,11 +46,13 @@ import android.database.ContentObserver; import android.graphics.Rect; import android.graphics.Region; import android.net.Uri; +import android.os.Binder; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Looper; +import android.os.Process; import android.os.RemoteException; import android.os.ResultReceiver; import android.os.SystemClock; @@ -450,6 +452,16 @@ public class InputMethodService extends AbstractInputMethodService { @Nullable private InlineSuggestionsRequestInfo mInlineSuggestionsRequestInfo = null; + /** + * An opaque {@link Binder} token of window requesting {@link InputMethodImpl#showSoftInput} + * The original app window token is passed from client app window. + * {@link com.android.server.inputmethod.InputMethodManagerService} creates a unique dummy + * token to identify this window. + * This dummy token is only valid for a single call to {@link InputMethodImpl#showSoftInput}, + * after which it is set null until next call. + */ + private IBinder mCurShowInputToken; + private final Handler mHandler = new Handler(Looper.getMainLooper(), null, true); final ViewTreeObserver.OnComputeInternalInsetsListener mInsetsComputer = info -> { @@ -491,6 +503,9 @@ public class InputMethodService extends AbstractInputMethodService { * all of the standard behavior for an input method. */ public class InputMethodImpl extends AbstractInputMethodImpl { + + private boolean mSystemCallingShowSoftInput; + /** * {@inheritDoc} * @hide @@ -657,6 +672,21 @@ public class InputMethodService extends AbstractInputMethodService { } } + /** + * {@inheritDoc} + * @hide + */ + @MainThread + @Override + public void showSoftInputWithToken(int flags, ResultReceiver resultReceiver, + IBinder showInputToken) { + mSystemCallingShowSoftInput = true; + mCurShowInputToken = showInputToken; + showSoftInput(flags, resultReceiver); + mCurShowInputToken = null; + mSystemCallingShowSoftInput = false; + } + /** * {@inheritDoc} */ @@ -664,6 +694,13 @@ public class InputMethodService extends AbstractInputMethodService { @Override public void showSoftInput(int flags, ResultReceiver resultReceiver) { if (DEBUG) Log.v(TAG, "showSoftInput()"); + // TODO(b/148086656): Disallow IME developers from calling InputMethodImpl methods. + if (getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.R + && !mSystemCallingShowSoftInput) { + Log.e(TAG," IME shouldn't call showSoftInput on itself." + + " Use requestShowSelf(int) itself"); + return; + } final boolean wasVisible = mIsPreRendered ? mDecorViewVisible && mWindowVisible : isInputViewShown(); if (dispatchOnShowInputRequested(flags, false)) { @@ -698,6 +735,15 @@ public class InputMethodService extends AbstractInputMethodService { public void changeInputMethodSubtype(InputMethodSubtype subtype) { dispatchOnCurrentInputMethodSubtypeChanged(subtype); } + + /** + * {@inheritDoc} + * @hide + */ + @Override + public void setCurrentShowInputToken(IBinder showInputToken) { + mCurShowInputToken = showInputToken; + } } // TODO(b/137800469): Add detailed docs explaining the inline suggestions process. @@ -2181,7 +2227,7 @@ public class InputMethodService extends AbstractInputMethodService { if (!isVisibilityAppliedUsingInsetsConsumer()) { return; } - mPrivOps.applyImeVisibility(setVisible); + mPrivOps.applyImeVisibility(mCurShowInputToken, setVisible); } private boolean isVisibilityAppliedUsingInsetsConsumer() { diff --git a/core/java/android/view/inputmethod/InputMethod.java b/core/java/android/view/inputmethod/InputMethod.java index cf494aec26f6f..91e15c1949b62 100644 --- a/core/java/android/view/inputmethod/InputMethod.java +++ b/core/java/android/view/inputmethod/InputMethod.java @@ -27,6 +27,7 @@ import android.os.IBinder; import android.os.RemoteException; import android.os.ResultReceiver; import android.util.Log; +import android.view.View; import android.view.autofill.AutofillId; import com.android.internal.inputmethod.IInputMethodPrivilegedOperations; @@ -298,7 +299,30 @@ public interface InputMethod { * until deliberated dismissed by the user in its UI. */ public static final int SHOW_FORCED = 0x00002; - + + /** + * Request that any soft input part of the input method be shown to the user. + * + * @param flags Provides additional information about the show request. + * Currently may be 0 or have the bit {@link #SHOW_EXPLICIT} set. + * @param resultReceiver The client requesting the show may wish to + * be told the impact of their request, which should be supplied here. + * The result code should be + * {@link InputMethodManager#RESULT_UNCHANGED_SHOWN InputMethodManager.RESULT_UNCHANGED_SHOWN}, + * {@link InputMethodManager#RESULT_UNCHANGED_HIDDEN InputMethodManager.RESULT_UNCHANGED_HIDDEN}, + * {@link InputMethodManager#RESULT_SHOWN InputMethodManager.RESULT_SHOWN}, or + * {@link InputMethodManager#RESULT_HIDDEN InputMethodManager.RESULT_HIDDEN}. + * @param showInputToken an opaque {@link android.os.Binder} token to identify which API call + * of {@link InputMethodManager#showSoftInput(View, int)} is associated with + * this callback. + * @hide + */ + @MainThread + default public void showSoftInputWithToken(int flags, ResultReceiver resultReceiver, + IBinder showInputToken) { + showSoftInput(flags, resultReceiver); + } + /** * Request that any soft input part of the input method be shown to the user. * @@ -314,7 +338,7 @@ public interface InputMethod { */ @MainThread public void showSoftInput(int flags, ResultReceiver resultReceiver); - + /** * Request that any soft input part of the input method be hidden from the user. * @param flags Provides additional information about the show request. @@ -336,4 +360,12 @@ public interface InputMethod { */ @MainThread public void changeInputMethodSubtype(InputMethodSubtype subtype); + + /** + * Update token of the client window requesting {@link #showSoftInput(int, ResultReceiver)} + * @param showInputToken dummy app window token for window requesting + * {@link InputMethodManager#showSoftInput(View, int)} + * @hide + */ + public void setCurrentShowInputToken(IBinder showInputToken); } diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index 904e736d214e4..d57668e0abba7 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -1617,7 +1617,8 @@ public final class InputMethodManager { } try { - return mService.showSoftInput(mClient, flags, resultReceiver); + return mService.showSoftInput( + mClient, view.getWindowToken(), flags, resultReceiver); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -1639,7 +1640,8 @@ public final class InputMethodManager { Log.w(TAG, "showSoftInputUnchecked() is a hidden method, which will be removed " + "soon. If you are using android.support.v7.widget.SearchView, please update " + "to version 26.0 or newer version."); - mService.showSoftInput(mClient, flags, resultReceiver); + mService.showSoftInput( + mClient, mCurRootView.getView().getWindowToken(), flags, resultReceiver); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl b/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl index e27ff00760545..20cd7c21d512f 100644 --- a/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl +++ b/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl @@ -41,5 +41,5 @@ interface IInputMethodPrivilegedOperations { boolean shouldOfferSwitchingToNextInputMethod(); void notifyUserAction(); void reportPreRendered(in EditorInfo info); - void applyImeVisibility(boolean setVisible); + void applyImeVisibility(IBinder showInputToken, boolean setVisible); } diff --git a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java index d42c607b98bfd..9eeef963de7f3 100644 --- a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java +++ b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java @@ -23,6 +23,7 @@ import android.net.Uri; import android.os.IBinder; import android.os.RemoteException; import android.util.Log; +import android.view.View; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodSubtype; @@ -368,18 +369,20 @@ public final class InputMethodPrivilegedOperations { } /** - * Calls {@link IInputMethodPrivilegedOperations#applyImeVisibility(boolean)}. + * Calls {@link IInputMethodPrivilegedOperations#applyImeVisibility(IBinder, boolean)}. * + * @param showInputToken dummy token that maps to window requesting + * {@link android.view.inputmethod.InputMethodManager#showSoftInput(View, int)} * @param setVisible {@code true} to set IME visible, else hidden. */ @AnyThread - public void applyImeVisibility(boolean setVisible) { + public void applyImeVisibility(IBinder showInputToken, boolean setVisible) { final IInputMethodPrivilegedOperations ops = mOps.getAndWarnIfNull(); if (ops == null) { return; } try { - ops.applyImeVisibility(setVisible); + ops.applyImeVisibility(showInputToken, setVisible); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/com/android/internal/view/IInputMethod.aidl b/core/java/com/android/internal/view/IInputMethod.aidl index 58aaa80b51bea..475a3214096d7 100644 --- a/core/java/com/android/internal/view/IInputMethod.aidl +++ b/core/java/com/android/internal/view/IInputMethod.aidl @@ -54,7 +54,7 @@ oneway interface IInputMethod { void revokeSession(IInputMethodSession session); - void showSoftInput(int flags, in ResultReceiver resultReceiver); + void showSoftInput(in IBinder showInputToken, int flags, in ResultReceiver resultReceiver); void hideSoftInput(int flags, in ResultReceiver resultReceiver); diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl index c29e823218eb2..0337ddd1ab49f 100644 --- a/core/java/com/android/internal/view/IInputMethodManager.aidl +++ b/core/java/com/android/internal/view/IInputMethodManager.aidl @@ -41,7 +41,7 @@ interface IInputMethodManager { boolean allowsImplicitlySelectedSubtypes); InputMethodSubtype getLastInputMethodSubtype(); - boolean showSoftInput(in IInputMethodClient client, int flags, + boolean showSoftInput(in IInputMethodClient client, IBinder windowToken, int flags, in ResultReceiver resultReceiver); boolean hideSoftInput(in IInputMethodClient client, int flags, in ResultReceiver resultReceiver); diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index 0bf65bd6f7392..905a94f572623 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -757,6 +757,14 @@ public class InputMethodManagerService extends IInputMethodManager.Stub @GuardedBy("mMethodMap") private final WeakHashMap mImeTargetWindowMap = new WeakHashMap<>(); + /** + * Map of generated token to windowToken that is requesting + * {@link InputMethodManager#showSoftInput(View, int)}. + * This map tracks origin of showSoftInput requests. + */ + @GuardedBy("mMethodMap") + private final WeakHashMap mShowRequestWindowMap = new WeakHashMap<>(); + /** * A ring buffer to store the history of {@link StartInputInfo}. */ @@ -974,7 +982,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub hideCurrentInputLocked(0, null); mShowRequested = showRequested; } else if (mShowRequested) { - showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null); + showCurrentInputLocked( + mCurFocusedWindow, InputMethodManager.SHOW_IMPLICIT, null); } } else { boolean enabledChanged = false; @@ -2075,7 +2084,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub startInputToken, session, mCurInputContext, mCurAttribute)); if (mShowRequested) { if (DEBUG) Slog.v(TAG, "Attach new input asks to show input"); - showCurrentInputLocked(getAppShowFlags(), null); + showCurrentInputLocked(mCurFocusedWindow, getAppShowFlags(), null); } return new InputBindResult(InputBindResult.ResultCode.SUCCESS_WITH_IME_SESSION, session.session, (session.channel != null ? session.channel.dup() : null), @@ -2789,7 +2798,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } @Override - public boolean showSoftInput(IInputMethodClient client, int flags, + public boolean showSoftInput(IInputMethodClient client, IBinder windowToken, int flags, ResultReceiver resultReceiver) { int uid = Binder.getCallingUid(); synchronized (mMethodMap) { @@ -2814,7 +2823,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } if (DEBUG) Slog.v(TAG, "Client requesting input be shown"); - return showCurrentInputLocked(flags, resultReceiver); + return showCurrentInputLocked(windowToken, flags, resultReceiver); } finally { Binder.restoreCallingIdentity(ident); } @@ -2822,7 +2831,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } @GuardedBy("mMethodMap") - boolean showCurrentInputLocked(int flags, ResultReceiver resultReceiver) { + boolean showCurrentInputLocked(IBinder windowToken, int flags, ResultReceiver resultReceiver) { mShowRequested = true; if (mAccessibilityRequestingNoSoftKeyboard) { return false; @@ -2842,9 +2851,12 @@ public class InputMethodManagerService extends IInputMethodManager.Stub boolean res = false; if (mCurMethod != null) { if (DEBUG) Slog.d(TAG, "showCurrentInputLocked: mCurToken=" + mCurToken); - executeOrSendMessage(mCurMethod, mCaller.obtainMessageIOO( + // create a dummy token for IMS so that IMS cannot inject windows into client app. + Binder showInputToken = new Binder(); + mShowRequestWindowMap.put(showInputToken, windowToken); + executeOrSendMessage(mCurMethod, mCaller.obtainMessageIOOO( MSG_SHOW_SOFT_INPUT, getImeShowFlags(), mCurMethod, - resultReceiver)); + resultReceiver, showInputToken)); mInputShown = true; if (mHaveConnection && !mVisibleBound) { bindCurrentInputMethodServiceLocked( @@ -3145,7 +3157,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub attribute, startInputFlags, startInputReason); didStart = true; } - showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null); + showCurrentInputLocked(windowToken, InputMethodManager.SHOW_IMPLICIT, null); } break; case LayoutParams.SOFT_INPUT_STATE_UNCHANGED: @@ -3171,7 +3183,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub attribute, startInputFlags, startInputReason); didStart = true; } - showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null); + showCurrentInputLocked(windowToken, InputMethodManager.SHOW_IMPLICIT, null); } else { Slog.e(TAG, "SOFT_INPUT_STATE_VISIBLE is ignored because" + " there is no focused view that also returns true from" @@ -3188,7 +3200,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub attribute, startInputFlags, startInputReason); didStart = true; } - showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null); + showCurrentInputLocked(windowToken, InputMethodManager.SHOW_IMPLICIT, null); } else { Slog.e(TAG, "SOFT_INPUT_STATE_ALWAYS_VISIBLE is ignored because" + " there is no focused view that also returns true from" @@ -3627,7 +3639,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } @BinderThread - private void applyImeVisibility(IBinder token, boolean setVisible) { + private void applyImeVisibility(IBinder token, IBinder windowToken, boolean setVisible) { synchronized (mMethodMap) { if (!calledWithValidTokenLocked(token)) { return; @@ -3644,7 +3656,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } else { // Send to window manager to show IME after IME layout finishes. - mWindowManagerInternal.showImePostLayout(mLastImeTargetWindow); + mWindowManagerInternal.showImePostLayout(mShowRequestWindowMap.get(windowToken)); } } } @@ -3695,7 +3707,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } long ident = Binder.clearCallingIdentity(); try { - showCurrentInputLocked(flags, null); + showCurrentInputLocked(mLastImeTargetWindow, flags, null); } finally { Binder.restoreCallingIdentity(ident); } @@ -3780,7 +3792,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub try { if (DEBUG) Slog.v(TAG, "Calling " + args.arg1 + ".showSoftInput(" + msg.arg1 + ", " + args.arg2 + ")"); - ((IInputMethod)args.arg1).showSoftInput(msg.arg1, (ResultReceiver)args.arg2); + ((IInputMethod) args.arg1).showSoftInput( + (IBinder) args.arg3, msg.arg1, (ResultReceiver) args.arg2); } catch (RemoteException e) { } args.recycle(); @@ -5346,8 +5359,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub @BinderThread @Override - public void applyImeVisibility(boolean setVisible) { - mImms.applyImeVisibility(mToken, setVisible); + public void applyImeVisibility(IBinder windowToken, boolean setVisible) { + mImms.applyImeVisibility(mToken, windowToken, setVisible); } } } diff --git a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java index f09795fbea010..d09c478e320f3 100644 --- a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java @@ -1449,7 +1449,8 @@ public final class MultiClientInputMethodManagerService { @BinderThread @Override public boolean showSoftInput( - IInputMethodClient client, int flags, ResultReceiver resultReceiver) { + IInputMethodClient client, IBinder token, int flags, + ResultReceiver resultReceiver) { final int callingUid = Binder.getCallingUid(); final int callingPid = Binder.getCallingPid(); final int userId = UserHandle.getUserId(callingUid);