Merge "Instantiate InputMethodManager for each display"

This commit is contained in:
TreeHugger Robot
2018-10-06 17:19:58 +00:00
committed by Android (Google) Code Review
7 changed files with 292 additions and 49 deletions

View File

@@ -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);
}});
registerService(Context.TEXT_SERVICES_MANAGER_SERVICE, TextServicesManager.class,

View File

@@ -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();
@@ -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.
@@ -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 = getDisplayId(view.getContext());
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,57 @@ 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);
}
private static int getDisplayId(Context context) {
final Display display = context.getDisplay();
return display != null ? display.getDisplayId() : Display.DEFAULT_DISPLAY;
}
/**
* 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 = getDisplayId(context);
// 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 +977,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 +1072,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 +1097,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 +1145,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 +1213,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 +1382,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 +1812,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 +1856,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 +1926,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 +1964,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 +2016,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 +2194,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 +2690,7 @@ public final class InputMethodManager {
sb.append(",windowFocus=" + view.hasWindowFocus());
sb.append(",autofillUiShowing=" + isAutofillUIShowing(view));
sb.append(",window=" + view.getWindowToken());
sb.append(",displayId=" + getDisplayId(view.getContext()));
sb.append(",temporaryDetach=" + view.isTemporarilyDetached());
return sb.toString();
}