Loading core/java/android/app/SystemServiceRegistry.java +7 −3 Original line number Diff line number Diff line Loading @@ -377,11 +377,15 @@ final class SystemServiceRegistry { return new DisplayManager(ctx.getOuterContext()); }}); // InputMethodManager has its own cache strategy based on display id to support apps that // still assume InputMethodManager is a per-process singleton and it's safe to directly // access internal fields via reflection. Hence directly use ServiceFetcher instead of // StaticServiceFetcher/CachedServiceFetcher. registerService(Context.INPUT_METHOD_SERVICE, InputMethodManager.class, new StaticServiceFetcher<InputMethodManager>() { new ServiceFetcher<InputMethodManager>() { @Override public InputMethodManager createService() { return InputMethodManager.getInstanceInternal(); public InputMethodManager getService(ContextImpl ctx) { return InputMethodManager.forContext(ctx.getOuterContext()); }}); registerService(Context.TEXT_SERVICES_MANAGER_SERVICE, TextServicesManager.class, Loading core/java/android/view/inputmethod/InputMethodManager.java +150 −15 Original line number Diff line number Diff line Loading @@ -48,6 +48,7 @@ import android.util.Pools.SimplePool; import android.util.PrintWriterPrinter; import android.util.Printer; import android.util.SparseArray; import android.view.Display; import android.view.InputChannel; import android.view.InputEvent; import android.view.InputEventSender; Loading Loading @@ -265,7 +266,7 @@ public final class InputMethodManager { * @hide */ public static void ensureDefaultInstanceForDefaultDisplayIfNecessary() { getInstanceInternal(); forContextInternal(Display.DEFAULT_DISPLAY, Looper.getMainLooper()); } private static final Object sLock = new Object(); Loading @@ -278,6 +279,17 @@ public final class InputMethodManager { @UnsupportedAppUsage static InputMethodManager sInstance; /** * Global map between display to {@link InputMethodManager}. * * <p>Currently this map works like a so-called leaky singleton. Once an instance is registered * for the associated display ID, that instance will never be garbage collected.</p> * * <p>TODO(Bug 116699479): Implement instance clean up mechanism.</p> */ @GuardedBy("sLock") private static final SparseArray<InputMethodManager> sInstanceMap = new SparseArray<>(); /** * @hide Flag for IInputMethodManager.windowGainedFocus: a view in * the window has input focus. Loading Loading @@ -335,6 +347,8 @@ public final class InputMethodManager { // Our generic input connection if the current target does not have its own. final IInputContext mIInputContext; private final int mDisplayId; /** * True if this input method client is active, initially false. */ Loading Loading @@ -452,6 +466,29 @@ public final class InputMethodManager { return afm != null && afm.isAutofillUiShowing(); } /** * Checks the consistency between {@link InputMethodManager} state and {@link View} state. * * @param view {@link View} to be checked * @return {@code true} if {@code view} is not {@code null} and there is a {@link Context} * mismatch between {@link InputMethodManager} and {@code view} */ private boolean shouldDispatchToViewContext(@Nullable View view) { if (view == null) { return false; } final int viewDisplayId = view.getContext().getDisplayId(); if (viewDisplayId != mDisplayId) { Log.w(TAG, "b/117267690: Context mismatch found. view=" + view + " belongs to" + " displayId=" + viewDisplayId + " but InputMethodManager belongs to displayId=" + mDisplayId + ". Use the right InputMethodManager instance to avoid performance overhead.", new Throwable()); return true; } return false; } private static boolean canStartInput(View servedView) { // We can start input ether the servedView has window focus // or the activity is showing autofill ui. Loading Loading @@ -733,33 +770,52 @@ public final class InputMethodManager { }); } InputMethodManager(Looper looper) throws ServiceNotFoundException { InputMethodManager(int displayId, Looper looper) throws ServiceNotFoundException { mService = getIInputMethodManager(); mMainLooper = looper; mH = new H(looper); mDisplayId = displayId; mIInputContext = new ControlledInputConnectionWrapper(looper, mDummyInputConnection, this); } /** * Retrieve the global {@link InputMethodManager} instance, creating it if it doesn't already * exist. * Retrieve an instance for the given {@link Context}, creating it if it doesn't already exist. * * @return global {@link InputMethodManager} instance * @param context {@link Context} for which IME APIs need to work * @return {@link InputMethodManager} instance * @hide */ public static InputMethodManager getInstanceInternal() { @Nullable public static InputMethodManager forContext(Context context) { final int displayId = context.getDisplayId(); // For better backward compatibility, we always use Looper.getMainLooper() for the default // display case. final Looper looper = displayId == Display.DEFAULT_DISPLAY ? Looper.getMainLooper() : context.getMainLooper(); return forContextInternal(displayId, looper); } @Nullable private static InputMethodManager forContextInternal(int displayId, Looper looper) { final boolean isDefaultDisplay = displayId == Display.DEFAULT_DISPLAY; synchronized (sLock) { if (sInstance == null) { InputMethodManager instance = sInstanceMap.get(displayId); if (instance != null) { return instance; } try { final InputMethodManager imm = new InputMethodManager(Looper.getMainLooper()); imm.mService.addClient(imm.mClient, imm.mIInputContext); sInstance = imm; instance = new InputMethodManager(displayId, looper); instance.mService.addClient(instance.mClient, instance.mIInputContext, displayId); } catch (ServiceNotFoundException | RemoteException e) { throw new IllegalStateException(e); } // For backward compatibility, store the instance also to sInstance for default display. if (sInstance == null && isDefaultDisplay) { sInstance = instance; } return sInstance; sInstanceMap.put(displayId, instance); return instance; } } Loading Loading @@ -916,6 +972,11 @@ public final class InputMethodManager { * input method. */ public boolean isActive(View view) { // Re-dispatch if there is a context mismatch. if (shouldDispatchToViewContext(view)) { return view.getContext().getSystemService(InputMethodManager.class).isActive(view); } checkFocus(); synchronized (mH) { return (mServedView == view Loading Loading @@ -1006,6 +1067,13 @@ public final class InputMethodManager { } public void displayCompletions(View view, CompletionInfo[] completions) { // Re-dispatch if there is a context mismatch. if (shouldDispatchToViewContext(view)) { view.getContext().getSystemService(InputMethodManager.class) .displayCompletions(view, completions); return; } checkFocus(); synchronized (mH) { if (mServedView != view && (mServedView == null Loading @@ -1024,6 +1092,13 @@ public final class InputMethodManager { } public void updateExtractedText(View view, int token, ExtractedText text) { // Re-dispatch if there is a context mismatch. if (shouldDispatchToViewContext(view)) { view.getContext().getSystemService(InputMethodManager.class) .updateExtractedText(view, token, text); return; } checkFocus(); synchronized (mH) { if (mServedView != view && (mServedView == null Loading Loading @@ -1065,6 +1140,12 @@ public final class InputMethodManager { * 0 or have the {@link #SHOW_IMPLICIT} bit set. */ public boolean showSoftInput(View view, int flags) { // Re-dispatch if there is a context mismatch. if (shouldDispatchToViewContext(view)) { return view.getContext().getSystemService(InputMethodManager.class) .showSoftInput(view, flags); } return showSoftInput(view, flags, null); } Loading Loading @@ -1127,6 +1208,12 @@ public final class InputMethodManager { * {@link #RESULT_HIDDEN}. */ public boolean showSoftInput(View view, int flags, ResultReceiver resultReceiver) { // Re-dispatch if there is a context mismatch. if (shouldDispatchToViewContext(view)) { return view.getContext().getSystemService(InputMethodManager.class) .showSoftInput(view, flags, resultReceiver); } checkFocus(); synchronized (mH) { if (mServedView != view && (mServedView == null Loading Loading @@ -1290,6 +1377,12 @@ public final class InputMethodManager { * @param view The view whose text has changed. */ public void restartInput(View view) { // Re-dispatch if there is a context mismatch. if (shouldDispatchToViewContext(view)) { view.getContext().getSystemService(InputMethodManager.class).restartInput(view); return; } checkFocus(); synchronized (mH) { if (mServedView != view && (mServedView == null Loading Loading @@ -1714,6 +1807,13 @@ public final class InputMethodManager { */ public void updateSelection(View view, int selStart, int selEnd, int candidatesStart, int candidatesEnd) { // Re-dispatch if there is a context mismatch. if (shouldDispatchToViewContext(view)) { view.getContext().getSystemService(InputMethodManager.class) .updateSelection(view, selStart, selEnd, candidatesStart, candidatesEnd); return; } checkFocus(); synchronized (mH) { if ((mServedView != view && (mServedView == null Loading Loading @@ -1751,6 +1851,12 @@ public final class InputMethodManager { * Notify the event when the user tapped or clicked the text view. */ public void viewClicked(View view) { // Re-dispatch if there is a context mismatch. if (shouldDispatchToViewContext(view)) { view.getContext().getSystemService(InputMethodManager.class).viewClicked(view); return; } final boolean focusChanged = mServedView != mNextServedView; checkFocus(); synchronized (mH) { Loading Loading @@ -1815,6 +1921,13 @@ public final class InputMethodManager { */ @Deprecated public void updateCursor(View view, int left, int top, int right, int bottom) { // Re-dispatch if there is a context mismatch. if (shouldDispatchToViewContext(view)) { view.getContext().getSystemService(InputMethodManager.class) .updateCursor(view, left, top, right, bottom); return; } checkFocus(); synchronized (mH) { if ((mServedView != view && (mServedView == null Loading Loading @@ -1846,6 +1959,13 @@ public final class InputMethodManager { if (view == null || cursorAnchorInfo == null) { return; } // Re-dispatch if there is a context mismatch. if (shouldDispatchToViewContext(view)) { view.getContext().getSystemService(InputMethodManager.class) .updateCursorAnchorInfo(view, cursorAnchorInfo); return; } checkFocus(); synchronized (mH) { if ((mServedView != view && Loading Loading @@ -1891,6 +2011,13 @@ public final class InputMethodManager { * @param data Any data to include with the command. */ public void sendAppPrivateCommand(View view, String action, Bundle data) { // Re-dispatch if there is a context mismatch. if (shouldDispatchToViewContext(view)) { view.getContext().getSystemService(InputMethodManager.class) .sendAppPrivateCommand(view, action, data); return; } checkFocus(); synchronized (mH) { if ((mServedView != view && (mServedView == null Loading Loading @@ -2062,6 +2189,13 @@ public final class InputMethodManager { */ public void dispatchKeyEventFromInputMethod(@Nullable View targetView, @NonNull KeyEvent event) { // Re-dispatch if there is a context mismatch. if (shouldDispatchToViewContext(targetView)) { targetView.getContext().getSystemService(InputMethodManager.class) .dispatchKeyEventFromInputMethod(targetView, event); return; } synchronized (mH) { ViewRootImpl viewRootImpl = targetView != null ? targetView.getViewRootImpl() : null; if (viewRootImpl == null) { Loading Loading @@ -2551,6 +2685,7 @@ public final class InputMethodManager { sb.append(",windowFocus=" + view.hasWindowFocus()); sb.append(",autofillUiShowing=" + isAutofillUIShowing(view)); sb.append(",window=" + view.getWindowToken()); sb.append(",displayId=" + view.getContext().getDisplayId()); sb.append(",temporaryDetach=" + view.isTemporarilyDetached()); return sb.toString(); } Loading core/java/com/android/internal/view/IInputMethodManager.aidl +2 −1 Original line number Diff line number Diff line Loading @@ -31,7 +31,8 @@ import com.android.internal.view.IInputMethodClient; * applications. */ interface IInputMethodManager { void addClient(in IInputMethodClient client, in IInputContext inputContext); void addClient(in IInputMethodClient client, in IInputContext inputContext, int untrustedDisplayId); // TODO: Use ParceledListSlice instead List<InputMethodInfo> getInputMethodList(); Loading core/java/com/android/internal/view/InputBindResult.java +28 −1 Original line number Diff line number Diff line Loading @@ -51,6 +51,9 @@ public final class InputBindResult implements Parcelable { ResultCode.ERROR_INVALID_USER, ResultCode.ERROR_NULL_EDITOR_INFO, ResultCode.ERROR_NOT_IME_TARGET_WINDOW, ResultCode.ERROR_NO_EDITOR, ResultCode.ERROR_DISPLAY_ID_MISMATCH, ResultCode.ERROR_INVALID_DISPLAY_ID, }) public @interface ResultCode { /** Loading Loading @@ -139,13 +142,22 @@ public final class InputBindResult implements Parcelable { * The client should try to restart input when its {@link android.view.Window} is focused * again.</p> * * @see com.android.server.wm.WindowManagerInternal#isInputMethodClientFocus(int, int) * @see com.android.server.wm.WindowManagerInternal#isInputMethodClientFocus(int, int, int) */ int ERROR_NOT_IME_TARGET_WINDOW = 11; /** * Indicates that focused view in the current window is not an editor. */ int ERROR_NO_EDITOR = 12; /** * Indicates that there is a mismatch in display ID between IME client and focused Window. */ int ERROR_DISPLAY_ID_MISMATCH = 13; /** * Indicates that current IME client is no longer allowed to access to the associated * display. */ int ERROR_INVALID_DISPLAY_ID = 14; } @ResultCode Loading Loading @@ -271,6 +283,10 @@ public final class InputBindResult implements Parcelable { return "ERROR_NULL_EDITOR_INFO"; case ResultCode.ERROR_NOT_IME_TARGET_WINDOW: return "ERROR_NOT_IME_TARGET_WINDOW"; case ResultCode.ERROR_DISPLAY_ID_MISMATCH: return "ERROR_DISPLAY_ID_MISMATCH"; case ResultCode.ERROR_INVALID_DISPLAY_ID: return "ERROR_INVALID_DISPLAY_ID"; default: return "Unknown(" + result + ")"; } Loading Loading @@ -316,4 +332,15 @@ public final class InputBindResult implements Parcelable { */ public static final InputBindResult INVALID_USER = error(ResultCode.ERROR_INVALID_USER); /** * Predefined error object for {@link ResultCode#ERROR_DISPLAY_ID_MISMATCH}. */ public static final InputBindResult DISPLAY_ID_MISMATCH = error(ResultCode.ERROR_DISPLAY_ID_MISMATCH); /** * Predefined error object for {@link ResultCode#ERROR_INVALID_DISPLAY_ID}. */ public static final InputBindResult INVALID_DISPLAY_ID = error(ResultCode.ERROR_INVALID_DISPLAY_ID); } services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +64 −17 Original line number Diff line number Diff line Loading @@ -421,6 +421,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub final IInputContext inputContext; final int uid; final int pid; final int selfReportedDisplayId; final InputBinding binding; final ClientDeathRecipient clientDeathRecipient; Loading @@ -430,16 +431,18 @@ public class InputMethodManagerService extends IInputMethodManager.Stub @Override public String toString() { return "ClientState{" + Integer.toHexString( System.identityHashCode(this)) + " uid " + uid + " pid " + pid + "}"; System.identityHashCode(this)) + " uid=" + uid + " pid=" + pid + " displayId=" + selfReportedDisplayId + "}"; } ClientState(IInputMethodClient _client, IInputContext _inputContext, int _uid, int _pid, ClientDeathRecipient _clientDeathRecipient) { int _uid, int _pid, int _selfReportedDisplayId, ClientDeathRecipient _clientDeathRecipient) { client = _client; inputContext = _inputContext; uid = _uid; pid = _pid; selfReportedDisplayId = _selfReportedDisplayId; binding = new InputBinding(null, inputContext.asBinder(), uid, pid); clientDeathRecipient = _clientDeathRecipient; } Loading Loading @@ -1745,15 +1748,21 @@ public class InputMethodManagerService extends IInputMethodManager.Stub * process * @param inputContext communication channel for the dummy * {@link android.view.inputmethod.InputConnection} * @param selfReportedDisplayId self-reported display ID to which the client is associated. * Whether the client is still allowed to access to this display * or not needs to be evaluated every time the client interacts * with the display */ @Override public void addClient(IInputMethodClient client, IInputContext inputContext) { public void addClient(IInputMethodClient client, IInputContext inputContext, int selfReportedDisplayId) { final int callerUid = Binder.getCallingUid(); final int callerPid = Binder.getCallingPid(); synchronized (mMethodMap) { // TODO: Optimize this linear search. for (ClientState state : mClients.values()) { if (state.uid == callerUid && state.pid == callerPid) { if (state.uid == callerUid && state.pid == callerPid && state.selfReportedDisplayId == selfReportedDisplayId) { throw new SecurityException("uid=" + callerUid + "/pid=" + callerPid + " is already registered"); } Loading @@ -1764,8 +1773,18 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } catch (RemoteException e) { throw new IllegalStateException(e); } mClients.put(client.asBinder(), new ClientState(client, inputContext, callerUid, callerPid, deathRecipient)); // We cannot fully avoid race conditions where the client UID already lost the access to // the given self-reported display ID, even if the client is not maliciously reporting // a fake display ID. Unconditionally returning SecurityException just because the // client doesn't pass display ID verification can cause many test failures hence not an // option right now. At the same time // context.getSystemService(InputMethodManager.class) // is expected to return a valid non-null instance at any time if we do not choose to // have the client crash. Thus we do not verify the display ID at all here. Instead we // later check the display ID every time the client needs to interact with the specified // display. mClients.put(client.asBinder(), new ClientState(client, inputContext, callerUid, callerPid, selfReportedDisplayId, deathRecipient)); } } Loading Loading @@ -1918,8 +1937,9 @@ public class InputMethodManagerService extends IInputMethodManager.Stub mCurAttribute = attribute; // Check if the input method is changing. final int displayId = mWindowManagerInternal.getDisplayIdForWindow( mCurFocusedWindow); // We expect the caller has already verified that the client is allowed to access this // display ID. final int displayId = mCurFocusedWindowClient.selfReportedDisplayId; if (mCurId != null && mCurId.equals(mCurMethodId) && displayId == mCurTokenDisplayId) { if (cs.curSession != null) { // Fast case: if we are already connected to the input method, Loading Loading @@ -1984,8 +2004,21 @@ public class InputMethodManagerService extends IInputMethodManager.Stub com.android.internal.R.string.input_method_binding_label); mCurIntent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivity( mContext, 0, new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS), 0)); final int displayId = mWindowManagerInternal.getDisplayIdForWindow(mCurFocusedWindow); if (mCurFocusedWindowClient == null) { // This can happen when called from systemRunning() or switchUserLocked(). In this case, // there really isn't an actual client yet. Let IME be on the default display. // TODO(Bug 117730713): Check if really need to bind to the IME or not. mCurTokenDisplayId = DEFAULT_DISPLAY; } else { if (!mWindowManagerInternal.isUidAllowedOnDisplay( mCurFocusedWindowClient.selfReportedDisplayId, mCurFocusedWindowClient.uid)) { // Wait, the client no longer has access to the display. return InputBindResult.INVALID_DISPLAY_ID; } final int displayId = mCurFocusedWindowClient.selfReportedDisplayId; mCurTokenDisplayId = (displayId != INVALID_DISPLAY) ? displayId : DEFAULT_DISPLAY; } if (bindCurrentInputMethodServiceLocked(mCurIntent, this, IME_CONNECTION_BIND_FLAGS)) { mLastBindTime = SystemClock.uptimeMillis(); Loading Loading @@ -2584,7 +2617,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub if (cs == null) { throw new IllegalArgumentException("unknown client " + client.asBinder()); } if (!mWindowManagerInternal.isInputMethodClientFocus(cs.uid, cs.pid)) { if (!mWindowManagerInternal.isInputMethodClientFocus(cs.uid, cs.pid, cs.selfReportedDisplayId)) { Slog.w(TAG, "Ignoring showSoftInput of uid " + uid + ": " + client); return false; } Loading Loading @@ -2668,7 +2702,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub if (cs == null) { throw new IllegalArgumentException("unknown client " + client.asBinder()); } if (!mWindowManagerInternal.isInputMethodClientFocus(cs.uid, cs.pid)) { if (!mWindowManagerInternal.isInputMethodClientFocus(cs.uid, cs.pid, cs.selfReportedDisplayId)) { if (DEBUG) { Slog.w(TAG, "Ignoring hideSoftInput of uid " + uid + ": " + client); } Loading Loading @@ -2767,6 +2802,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub InputBindResult res = null; long ident = Binder.clearCallingIdentity(); try { final int windowDisplayId = mWindowManagerInternal.getDisplayIdForWindow(windowToken); synchronized (mMethodMap) { if (DEBUG) Slog.v(TAG, "startInputOrWindowGainedFocusInternal: reason=" + InputMethodClient.getStartInputReason(startInputReason) Loading @@ -2785,8 +2822,15 @@ public class InputMethodManagerService extends IInputMethodManager.Stub throw new IllegalArgumentException("unknown client " + client.asBinder()); } if (cs.selfReportedDisplayId != windowDisplayId) { Slog.e(TAG, "startInputOrWindowGainedFocusInternal: display ID mismatch." + " from client:" + cs.selfReportedDisplayId + " from window:" + windowDisplayId); return InputBindResult.DISPLAY_ID_MISMATCH; } if (!mWindowManagerInternal.isInputMethodClientFocus(cs.uid, cs.pid)) { if (!mWindowManagerInternal.isInputMethodClientFocus(cs.uid, cs.pid, cs.selfReportedDisplayId)) { // Check with the window manager to make sure this client actually // has a window with focus. If not, reject. This is thread safe // because if the focus changes some time before or after, the Loading Loading @@ -2858,9 +2902,9 @@ public class InputMethodManagerService extends IInputMethodManager.Stub // If focused display changed, we should unbind current method // to make app window in previous display relayout after Ime // window token removed. final int newFocusDisplayId = mWindowManagerInternal.getDisplayIdForWindow(windowToken); if (newFocusDisplayId != mCurTokenDisplayId) { // Note that we can trust client's display ID as long as it matches // to the display ID obtained from the window. if (cs.selfReportedDisplayId != mCurTokenDisplayId) { unbindCurrentMethodLocked(); } } Loading Loading @@ -2958,6 +3002,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } private boolean canShowInputMethodPickerLocked(IInputMethodClient client) { // TODO(yukawa): multi-display support. final int uid = Binder.getCallingUid(); if (UserHandle.getAppId(uid) == Process.SYSTEM_UID) { return true; Loading Loading @@ -3034,6 +3079,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub @Override public void showInputMethodAndSubtypeEnablerFromClient( IInputMethodClient client, String inputMethodId) { // TODO(yukawa): Should we verify the display ID? if (!calledFromValidUser()) { return; } Loading Loading @@ -3229,6 +3275,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub */ @Override public int getInputMethodWindowVisibleHeight() { // TODO(yukawa): Should we verify the display ID? return mWindowManagerInternal.getInputMethodWindowVisibleHeight(mCurTokenDisplayId); } Loading Loading
core/java/android/app/SystemServiceRegistry.java +7 −3 Original line number Diff line number Diff line Loading @@ -377,11 +377,15 @@ final class SystemServiceRegistry { return new DisplayManager(ctx.getOuterContext()); }}); // InputMethodManager has its own cache strategy based on display id to support apps that // still assume InputMethodManager is a per-process singleton and it's safe to directly // access internal fields via reflection. Hence directly use ServiceFetcher instead of // StaticServiceFetcher/CachedServiceFetcher. registerService(Context.INPUT_METHOD_SERVICE, InputMethodManager.class, new StaticServiceFetcher<InputMethodManager>() { new ServiceFetcher<InputMethodManager>() { @Override public InputMethodManager createService() { return InputMethodManager.getInstanceInternal(); public InputMethodManager getService(ContextImpl ctx) { return InputMethodManager.forContext(ctx.getOuterContext()); }}); registerService(Context.TEXT_SERVICES_MANAGER_SERVICE, TextServicesManager.class, Loading
core/java/android/view/inputmethod/InputMethodManager.java +150 −15 Original line number Diff line number Diff line Loading @@ -48,6 +48,7 @@ import android.util.Pools.SimplePool; import android.util.PrintWriterPrinter; import android.util.Printer; import android.util.SparseArray; import android.view.Display; import android.view.InputChannel; import android.view.InputEvent; import android.view.InputEventSender; Loading Loading @@ -265,7 +266,7 @@ public final class InputMethodManager { * @hide */ public static void ensureDefaultInstanceForDefaultDisplayIfNecessary() { getInstanceInternal(); forContextInternal(Display.DEFAULT_DISPLAY, Looper.getMainLooper()); } private static final Object sLock = new Object(); Loading @@ -278,6 +279,17 @@ public final class InputMethodManager { @UnsupportedAppUsage static InputMethodManager sInstance; /** * Global map between display to {@link InputMethodManager}. * * <p>Currently this map works like a so-called leaky singleton. Once an instance is registered * for the associated display ID, that instance will never be garbage collected.</p> * * <p>TODO(Bug 116699479): Implement instance clean up mechanism.</p> */ @GuardedBy("sLock") private static final SparseArray<InputMethodManager> sInstanceMap = new SparseArray<>(); /** * @hide Flag for IInputMethodManager.windowGainedFocus: a view in * the window has input focus. Loading Loading @@ -335,6 +347,8 @@ public final class InputMethodManager { // Our generic input connection if the current target does not have its own. final IInputContext mIInputContext; private final int mDisplayId; /** * True if this input method client is active, initially false. */ Loading Loading @@ -452,6 +466,29 @@ public final class InputMethodManager { return afm != null && afm.isAutofillUiShowing(); } /** * Checks the consistency between {@link InputMethodManager} state and {@link View} state. * * @param view {@link View} to be checked * @return {@code true} if {@code view} is not {@code null} and there is a {@link Context} * mismatch between {@link InputMethodManager} and {@code view} */ private boolean shouldDispatchToViewContext(@Nullable View view) { if (view == null) { return false; } final int viewDisplayId = view.getContext().getDisplayId(); if (viewDisplayId != mDisplayId) { Log.w(TAG, "b/117267690: Context mismatch found. view=" + view + " belongs to" + " displayId=" + viewDisplayId + " but InputMethodManager belongs to displayId=" + mDisplayId + ". Use the right InputMethodManager instance to avoid performance overhead.", new Throwable()); return true; } return false; } private static boolean canStartInput(View servedView) { // We can start input ether the servedView has window focus // or the activity is showing autofill ui. Loading Loading @@ -733,33 +770,52 @@ public final class InputMethodManager { }); } InputMethodManager(Looper looper) throws ServiceNotFoundException { InputMethodManager(int displayId, Looper looper) throws ServiceNotFoundException { mService = getIInputMethodManager(); mMainLooper = looper; mH = new H(looper); mDisplayId = displayId; mIInputContext = new ControlledInputConnectionWrapper(looper, mDummyInputConnection, this); } /** * Retrieve the global {@link InputMethodManager} instance, creating it if it doesn't already * exist. * Retrieve an instance for the given {@link Context}, creating it if it doesn't already exist. * * @return global {@link InputMethodManager} instance * @param context {@link Context} for which IME APIs need to work * @return {@link InputMethodManager} instance * @hide */ public static InputMethodManager getInstanceInternal() { @Nullable public static InputMethodManager forContext(Context context) { final int displayId = context.getDisplayId(); // For better backward compatibility, we always use Looper.getMainLooper() for the default // display case. final Looper looper = displayId == Display.DEFAULT_DISPLAY ? Looper.getMainLooper() : context.getMainLooper(); return forContextInternal(displayId, looper); } @Nullable private static InputMethodManager forContextInternal(int displayId, Looper looper) { final boolean isDefaultDisplay = displayId == Display.DEFAULT_DISPLAY; synchronized (sLock) { if (sInstance == null) { InputMethodManager instance = sInstanceMap.get(displayId); if (instance != null) { return instance; } try { final InputMethodManager imm = new InputMethodManager(Looper.getMainLooper()); imm.mService.addClient(imm.mClient, imm.mIInputContext); sInstance = imm; instance = new InputMethodManager(displayId, looper); instance.mService.addClient(instance.mClient, instance.mIInputContext, displayId); } catch (ServiceNotFoundException | RemoteException e) { throw new IllegalStateException(e); } // For backward compatibility, store the instance also to sInstance for default display. if (sInstance == null && isDefaultDisplay) { sInstance = instance; } return sInstance; sInstanceMap.put(displayId, instance); return instance; } } Loading Loading @@ -916,6 +972,11 @@ public final class InputMethodManager { * input method. */ public boolean isActive(View view) { // Re-dispatch if there is a context mismatch. if (shouldDispatchToViewContext(view)) { return view.getContext().getSystemService(InputMethodManager.class).isActive(view); } checkFocus(); synchronized (mH) { return (mServedView == view Loading Loading @@ -1006,6 +1067,13 @@ public final class InputMethodManager { } public void displayCompletions(View view, CompletionInfo[] completions) { // Re-dispatch if there is a context mismatch. if (shouldDispatchToViewContext(view)) { view.getContext().getSystemService(InputMethodManager.class) .displayCompletions(view, completions); return; } checkFocus(); synchronized (mH) { if (mServedView != view && (mServedView == null Loading @@ -1024,6 +1092,13 @@ public final class InputMethodManager { } public void updateExtractedText(View view, int token, ExtractedText text) { // Re-dispatch if there is a context mismatch. if (shouldDispatchToViewContext(view)) { view.getContext().getSystemService(InputMethodManager.class) .updateExtractedText(view, token, text); return; } checkFocus(); synchronized (mH) { if (mServedView != view && (mServedView == null Loading Loading @@ -1065,6 +1140,12 @@ public final class InputMethodManager { * 0 or have the {@link #SHOW_IMPLICIT} bit set. */ public boolean showSoftInput(View view, int flags) { // Re-dispatch if there is a context mismatch. if (shouldDispatchToViewContext(view)) { return view.getContext().getSystemService(InputMethodManager.class) .showSoftInput(view, flags); } return showSoftInput(view, flags, null); } Loading Loading @@ -1127,6 +1208,12 @@ public final class InputMethodManager { * {@link #RESULT_HIDDEN}. */ public boolean showSoftInput(View view, int flags, ResultReceiver resultReceiver) { // Re-dispatch if there is a context mismatch. if (shouldDispatchToViewContext(view)) { return view.getContext().getSystemService(InputMethodManager.class) .showSoftInput(view, flags, resultReceiver); } checkFocus(); synchronized (mH) { if (mServedView != view && (mServedView == null Loading Loading @@ -1290,6 +1377,12 @@ public final class InputMethodManager { * @param view The view whose text has changed. */ public void restartInput(View view) { // Re-dispatch if there is a context mismatch. if (shouldDispatchToViewContext(view)) { view.getContext().getSystemService(InputMethodManager.class).restartInput(view); return; } checkFocus(); synchronized (mH) { if (mServedView != view && (mServedView == null Loading Loading @@ -1714,6 +1807,13 @@ public final class InputMethodManager { */ public void updateSelection(View view, int selStart, int selEnd, int candidatesStart, int candidatesEnd) { // Re-dispatch if there is a context mismatch. if (shouldDispatchToViewContext(view)) { view.getContext().getSystemService(InputMethodManager.class) .updateSelection(view, selStart, selEnd, candidatesStart, candidatesEnd); return; } checkFocus(); synchronized (mH) { if ((mServedView != view && (mServedView == null Loading Loading @@ -1751,6 +1851,12 @@ public final class InputMethodManager { * Notify the event when the user tapped or clicked the text view. */ public void viewClicked(View view) { // Re-dispatch if there is a context mismatch. if (shouldDispatchToViewContext(view)) { view.getContext().getSystemService(InputMethodManager.class).viewClicked(view); return; } final boolean focusChanged = mServedView != mNextServedView; checkFocus(); synchronized (mH) { Loading Loading @@ -1815,6 +1921,13 @@ public final class InputMethodManager { */ @Deprecated public void updateCursor(View view, int left, int top, int right, int bottom) { // Re-dispatch if there is a context mismatch. if (shouldDispatchToViewContext(view)) { view.getContext().getSystemService(InputMethodManager.class) .updateCursor(view, left, top, right, bottom); return; } checkFocus(); synchronized (mH) { if ((mServedView != view && (mServedView == null Loading Loading @@ -1846,6 +1959,13 @@ public final class InputMethodManager { if (view == null || cursorAnchorInfo == null) { return; } // Re-dispatch if there is a context mismatch. if (shouldDispatchToViewContext(view)) { view.getContext().getSystemService(InputMethodManager.class) .updateCursorAnchorInfo(view, cursorAnchorInfo); return; } checkFocus(); synchronized (mH) { if ((mServedView != view && Loading Loading @@ -1891,6 +2011,13 @@ public final class InputMethodManager { * @param data Any data to include with the command. */ public void sendAppPrivateCommand(View view, String action, Bundle data) { // Re-dispatch if there is a context mismatch. if (shouldDispatchToViewContext(view)) { view.getContext().getSystemService(InputMethodManager.class) .sendAppPrivateCommand(view, action, data); return; } checkFocus(); synchronized (mH) { if ((mServedView != view && (mServedView == null Loading Loading @@ -2062,6 +2189,13 @@ public final class InputMethodManager { */ public void dispatchKeyEventFromInputMethod(@Nullable View targetView, @NonNull KeyEvent event) { // Re-dispatch if there is a context mismatch. if (shouldDispatchToViewContext(targetView)) { targetView.getContext().getSystemService(InputMethodManager.class) .dispatchKeyEventFromInputMethod(targetView, event); return; } synchronized (mH) { ViewRootImpl viewRootImpl = targetView != null ? targetView.getViewRootImpl() : null; if (viewRootImpl == null) { Loading Loading @@ -2551,6 +2685,7 @@ public final class InputMethodManager { sb.append(",windowFocus=" + view.hasWindowFocus()); sb.append(",autofillUiShowing=" + isAutofillUIShowing(view)); sb.append(",window=" + view.getWindowToken()); sb.append(",displayId=" + view.getContext().getDisplayId()); sb.append(",temporaryDetach=" + view.isTemporarilyDetached()); return sb.toString(); } Loading
core/java/com/android/internal/view/IInputMethodManager.aidl +2 −1 Original line number Diff line number Diff line Loading @@ -31,7 +31,8 @@ import com.android.internal.view.IInputMethodClient; * applications. */ interface IInputMethodManager { void addClient(in IInputMethodClient client, in IInputContext inputContext); void addClient(in IInputMethodClient client, in IInputContext inputContext, int untrustedDisplayId); // TODO: Use ParceledListSlice instead List<InputMethodInfo> getInputMethodList(); Loading
core/java/com/android/internal/view/InputBindResult.java +28 −1 Original line number Diff line number Diff line Loading @@ -51,6 +51,9 @@ public final class InputBindResult implements Parcelable { ResultCode.ERROR_INVALID_USER, ResultCode.ERROR_NULL_EDITOR_INFO, ResultCode.ERROR_NOT_IME_TARGET_WINDOW, ResultCode.ERROR_NO_EDITOR, ResultCode.ERROR_DISPLAY_ID_MISMATCH, ResultCode.ERROR_INVALID_DISPLAY_ID, }) public @interface ResultCode { /** Loading Loading @@ -139,13 +142,22 @@ public final class InputBindResult implements Parcelable { * The client should try to restart input when its {@link android.view.Window} is focused * again.</p> * * @see com.android.server.wm.WindowManagerInternal#isInputMethodClientFocus(int, int) * @see com.android.server.wm.WindowManagerInternal#isInputMethodClientFocus(int, int, int) */ int ERROR_NOT_IME_TARGET_WINDOW = 11; /** * Indicates that focused view in the current window is not an editor. */ int ERROR_NO_EDITOR = 12; /** * Indicates that there is a mismatch in display ID between IME client and focused Window. */ int ERROR_DISPLAY_ID_MISMATCH = 13; /** * Indicates that current IME client is no longer allowed to access to the associated * display. */ int ERROR_INVALID_DISPLAY_ID = 14; } @ResultCode Loading Loading @@ -271,6 +283,10 @@ public final class InputBindResult implements Parcelable { return "ERROR_NULL_EDITOR_INFO"; case ResultCode.ERROR_NOT_IME_TARGET_WINDOW: return "ERROR_NOT_IME_TARGET_WINDOW"; case ResultCode.ERROR_DISPLAY_ID_MISMATCH: return "ERROR_DISPLAY_ID_MISMATCH"; case ResultCode.ERROR_INVALID_DISPLAY_ID: return "ERROR_INVALID_DISPLAY_ID"; default: return "Unknown(" + result + ")"; } Loading Loading @@ -316,4 +332,15 @@ public final class InputBindResult implements Parcelable { */ public static final InputBindResult INVALID_USER = error(ResultCode.ERROR_INVALID_USER); /** * Predefined error object for {@link ResultCode#ERROR_DISPLAY_ID_MISMATCH}. */ public static final InputBindResult DISPLAY_ID_MISMATCH = error(ResultCode.ERROR_DISPLAY_ID_MISMATCH); /** * Predefined error object for {@link ResultCode#ERROR_INVALID_DISPLAY_ID}. */ public static final InputBindResult INVALID_DISPLAY_ID = error(ResultCode.ERROR_INVALID_DISPLAY_ID); }
services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +64 −17 Original line number Diff line number Diff line Loading @@ -421,6 +421,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub final IInputContext inputContext; final int uid; final int pid; final int selfReportedDisplayId; final InputBinding binding; final ClientDeathRecipient clientDeathRecipient; Loading @@ -430,16 +431,18 @@ public class InputMethodManagerService extends IInputMethodManager.Stub @Override public String toString() { return "ClientState{" + Integer.toHexString( System.identityHashCode(this)) + " uid " + uid + " pid " + pid + "}"; System.identityHashCode(this)) + " uid=" + uid + " pid=" + pid + " displayId=" + selfReportedDisplayId + "}"; } ClientState(IInputMethodClient _client, IInputContext _inputContext, int _uid, int _pid, ClientDeathRecipient _clientDeathRecipient) { int _uid, int _pid, int _selfReportedDisplayId, ClientDeathRecipient _clientDeathRecipient) { client = _client; inputContext = _inputContext; uid = _uid; pid = _pid; selfReportedDisplayId = _selfReportedDisplayId; binding = new InputBinding(null, inputContext.asBinder(), uid, pid); clientDeathRecipient = _clientDeathRecipient; } Loading Loading @@ -1745,15 +1748,21 @@ public class InputMethodManagerService extends IInputMethodManager.Stub * process * @param inputContext communication channel for the dummy * {@link android.view.inputmethod.InputConnection} * @param selfReportedDisplayId self-reported display ID to which the client is associated. * Whether the client is still allowed to access to this display * or not needs to be evaluated every time the client interacts * with the display */ @Override public void addClient(IInputMethodClient client, IInputContext inputContext) { public void addClient(IInputMethodClient client, IInputContext inputContext, int selfReportedDisplayId) { final int callerUid = Binder.getCallingUid(); final int callerPid = Binder.getCallingPid(); synchronized (mMethodMap) { // TODO: Optimize this linear search. for (ClientState state : mClients.values()) { if (state.uid == callerUid && state.pid == callerPid) { if (state.uid == callerUid && state.pid == callerPid && state.selfReportedDisplayId == selfReportedDisplayId) { throw new SecurityException("uid=" + callerUid + "/pid=" + callerPid + " is already registered"); } Loading @@ -1764,8 +1773,18 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } catch (RemoteException e) { throw new IllegalStateException(e); } mClients.put(client.asBinder(), new ClientState(client, inputContext, callerUid, callerPid, deathRecipient)); // We cannot fully avoid race conditions where the client UID already lost the access to // the given self-reported display ID, even if the client is not maliciously reporting // a fake display ID. Unconditionally returning SecurityException just because the // client doesn't pass display ID verification can cause many test failures hence not an // option right now. At the same time // context.getSystemService(InputMethodManager.class) // is expected to return a valid non-null instance at any time if we do not choose to // have the client crash. Thus we do not verify the display ID at all here. Instead we // later check the display ID every time the client needs to interact with the specified // display. mClients.put(client.asBinder(), new ClientState(client, inputContext, callerUid, callerPid, selfReportedDisplayId, deathRecipient)); } } Loading Loading @@ -1918,8 +1937,9 @@ public class InputMethodManagerService extends IInputMethodManager.Stub mCurAttribute = attribute; // Check if the input method is changing. final int displayId = mWindowManagerInternal.getDisplayIdForWindow( mCurFocusedWindow); // We expect the caller has already verified that the client is allowed to access this // display ID. final int displayId = mCurFocusedWindowClient.selfReportedDisplayId; if (mCurId != null && mCurId.equals(mCurMethodId) && displayId == mCurTokenDisplayId) { if (cs.curSession != null) { // Fast case: if we are already connected to the input method, Loading Loading @@ -1984,8 +2004,21 @@ public class InputMethodManagerService extends IInputMethodManager.Stub com.android.internal.R.string.input_method_binding_label); mCurIntent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivity( mContext, 0, new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS), 0)); final int displayId = mWindowManagerInternal.getDisplayIdForWindow(mCurFocusedWindow); if (mCurFocusedWindowClient == null) { // This can happen when called from systemRunning() or switchUserLocked(). In this case, // there really isn't an actual client yet. Let IME be on the default display. // TODO(Bug 117730713): Check if really need to bind to the IME or not. mCurTokenDisplayId = DEFAULT_DISPLAY; } else { if (!mWindowManagerInternal.isUidAllowedOnDisplay( mCurFocusedWindowClient.selfReportedDisplayId, mCurFocusedWindowClient.uid)) { // Wait, the client no longer has access to the display. return InputBindResult.INVALID_DISPLAY_ID; } final int displayId = mCurFocusedWindowClient.selfReportedDisplayId; mCurTokenDisplayId = (displayId != INVALID_DISPLAY) ? displayId : DEFAULT_DISPLAY; } if (bindCurrentInputMethodServiceLocked(mCurIntent, this, IME_CONNECTION_BIND_FLAGS)) { mLastBindTime = SystemClock.uptimeMillis(); Loading Loading @@ -2584,7 +2617,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub if (cs == null) { throw new IllegalArgumentException("unknown client " + client.asBinder()); } if (!mWindowManagerInternal.isInputMethodClientFocus(cs.uid, cs.pid)) { if (!mWindowManagerInternal.isInputMethodClientFocus(cs.uid, cs.pid, cs.selfReportedDisplayId)) { Slog.w(TAG, "Ignoring showSoftInput of uid " + uid + ": " + client); return false; } Loading Loading @@ -2668,7 +2702,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub if (cs == null) { throw new IllegalArgumentException("unknown client " + client.asBinder()); } if (!mWindowManagerInternal.isInputMethodClientFocus(cs.uid, cs.pid)) { if (!mWindowManagerInternal.isInputMethodClientFocus(cs.uid, cs.pid, cs.selfReportedDisplayId)) { if (DEBUG) { Slog.w(TAG, "Ignoring hideSoftInput of uid " + uid + ": " + client); } Loading Loading @@ -2767,6 +2802,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub InputBindResult res = null; long ident = Binder.clearCallingIdentity(); try { final int windowDisplayId = mWindowManagerInternal.getDisplayIdForWindow(windowToken); synchronized (mMethodMap) { if (DEBUG) Slog.v(TAG, "startInputOrWindowGainedFocusInternal: reason=" + InputMethodClient.getStartInputReason(startInputReason) Loading @@ -2785,8 +2822,15 @@ public class InputMethodManagerService extends IInputMethodManager.Stub throw new IllegalArgumentException("unknown client " + client.asBinder()); } if (cs.selfReportedDisplayId != windowDisplayId) { Slog.e(TAG, "startInputOrWindowGainedFocusInternal: display ID mismatch." + " from client:" + cs.selfReportedDisplayId + " from window:" + windowDisplayId); return InputBindResult.DISPLAY_ID_MISMATCH; } if (!mWindowManagerInternal.isInputMethodClientFocus(cs.uid, cs.pid)) { if (!mWindowManagerInternal.isInputMethodClientFocus(cs.uid, cs.pid, cs.selfReportedDisplayId)) { // Check with the window manager to make sure this client actually // has a window with focus. If not, reject. This is thread safe // because if the focus changes some time before or after, the Loading Loading @@ -2858,9 +2902,9 @@ public class InputMethodManagerService extends IInputMethodManager.Stub // If focused display changed, we should unbind current method // to make app window in previous display relayout after Ime // window token removed. final int newFocusDisplayId = mWindowManagerInternal.getDisplayIdForWindow(windowToken); if (newFocusDisplayId != mCurTokenDisplayId) { // Note that we can trust client's display ID as long as it matches // to the display ID obtained from the window. if (cs.selfReportedDisplayId != mCurTokenDisplayId) { unbindCurrentMethodLocked(); } } Loading Loading @@ -2958,6 +3002,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } private boolean canShowInputMethodPickerLocked(IInputMethodClient client) { // TODO(yukawa): multi-display support. final int uid = Binder.getCallingUid(); if (UserHandle.getAppId(uid) == Process.SYSTEM_UID) { return true; Loading Loading @@ -3034,6 +3079,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub @Override public void showInputMethodAndSubtypeEnablerFromClient( IInputMethodClient client, String inputMethodId) { // TODO(yukawa): Should we verify the display ID? if (!calledFromValidUser()) { return; } Loading Loading @@ -3229,6 +3275,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub */ @Override public int getInputMethodWindowVisibleHeight() { // TODO(yukawa): Should we verify the display ID? return mWindowManagerInternal.getInputMethodWindowVisibleHeight(mCurTokenDisplayId); } Loading