diff options
| author | TreeHugger Robot <treehugger-gerrit@google.com> | 2018-10-16 08:02:49 +0000 |
|---|---|---|
| committer | Android (Google) Code Review <android-gerrit@google.com> | 2018-10-16 08:02:49 +0000 |
| commit | 1728c04cae941749bee197a2f0f0c8b06a7a1f75 (patch) | |
| tree | 1ddf00dec9cc54c0bb48c9f44ab63d1354f19257 /core/java | |
| parent | 77f7614558e95e486664314b4fb24a6b5d22f6c0 (diff) | |
| parent | 4052a10f2970f83d40bf5a45f3632cd63d084e51 (diff) | |
Merge "Instantiate InputMethodManager for each display (2nd try)"
Diffstat (limited to 'core/java')
4 files changed, 187 insertions, 20 deletions
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index 0044005c51f2..77cebc8f408d 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -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, diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index ca2ccaf224db..e8e4b4aba0d4 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -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; @@ -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(); @@ -279,6 +280,17 @@ public final class InputMethodManager { 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. */ @@ -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. */ @@ -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. @@ -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) { - try { - final InputMethodManager imm = new InputMethodManager(Looper.getMainLooper()); - imm.mService.addClient(imm.mClient, imm.mIInputContext); - sInstance = imm; - } catch (ServiceNotFoundException | RemoteException e) { - throw new IllegalStateException(e); - } + InputMethodManager instance = sInstanceMap.get(displayId); + if (instance != null) { + return instance; } - return sInstance; + try { + 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; + } + sInstanceMap.put(displayId, instance); + return instance; } } @@ -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 @@ -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 @@ -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 @@ -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); } @@ -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 @@ -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 @@ -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 @@ -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) { @@ -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 @@ -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 && @@ -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 @@ -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) { @@ -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(); } diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl index 5f1243f37542..dceacda5d4a3 100644 --- a/core/java/com/android/internal/view/IInputMethodManager.aidl +++ b/core/java/com/android/internal/view/IInputMethodManager.aidl @@ -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(); diff --git a/core/java/com/android/internal/view/InputBindResult.java b/core/java/com/android/internal/view/InputBindResult.java index 101fd41f2925..ec8e8dacb9db 100644 --- a/core/java/com/android/internal/view/InputBindResult.java +++ b/core/java/com/android/internal/view/InputBindResult.java @@ -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 { /** @@ -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 @@ -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 + ")"; } @@ -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); } |
