Introduce IInputMethodInvoker

This CL introduces a wrapper class IInputMethodInvoker to consolidate
RemoteException handlings out from InputMethodManagerService around
IInputMethod.

Basically there should be no observable behavior change, except that
we logcat messages upon RemoteException when issuing IInputMethod IPCs
are now shown only when

  InputMethodManagerService#DEBUG

is configured to be true or the exception is not DeadObjectException.

Hopefully we can still easily find that the IME was being killed from
other messages in logcat.

Bug: 215609403
Test: presubmit
Change-Id: Id636d35f3b82a6376c9f370e2f65692f28e545eb
This commit is contained in:
Yohei Yukawa
2022-01-24 08:22:26 -08:00
parent 8860e4044f
commit 67b9a09cb6
3 changed files with 273 additions and 93 deletions

View File

@@ -0,0 +1,224 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.inputmethod;
import android.annotation.AnyThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Binder;
import android.os.DeadObjectException;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.util.Slog;
import android.view.InputChannel;
import android.view.MotionEvent;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputBinding;
import android.view.inputmethod.InputMethodSubtype;
import com.android.internal.inputmethod.IInputMethodPrivilegedOperations;
import com.android.internal.view.IInlineSuggestionsRequestCallback;
import com.android.internal.view.IInputContext;
import com.android.internal.view.IInputMethod;
import com.android.internal.view.IInputMethodSession;
import com.android.internal.view.IInputSessionCallback;
import com.android.internal.view.InlineSuggestionsRequestInfo;
import java.util.List;
/**
* A wrapper class to invoke IPCs defined in {@link IInputMethod}.
*/
final class IInputMethodInvoker {
private static final String TAG = InputMethodManagerService.TAG;
private static final boolean DEBUG = InputMethodManagerService.DEBUG;
@AnyThread
@Nullable
static IInputMethodInvoker create(@Nullable IInputMethod inputMethod) {
if (inputMethod == null) {
return null;
}
if (!Binder.isProxy(inputMethod)) {
// IInputMethodInvoker must be used only within the system_server and InputMethodService
// must not be running in the system_server. Therefore, "inputMethod" must be a Proxy.
throw new UnsupportedOperationException(inputMethod + " must have been a BinderProxy.");
}
return new IInputMethodInvoker(inputMethod);
}
/**
* A simplified version of {@link android.os.Debug#getCaller()}.
*
* @return method name of the caller.
*/
@AnyThread
private static String getCallerMethodName() {
final StackTraceElement[] callStack = Thread.currentThread().getStackTrace();
if (callStack.length <= 4) {
return "<bottom of call stack>";
}
return callStack[4].getMethodName();
}
@AnyThread
private static void logRemoteException(@NonNull RemoteException e) {
if (DEBUG || !(e instanceof DeadObjectException)) {
Slog.w(TAG, "IPC failed at IInputMethodInvoker#" + getCallerMethodName(), e);
}
}
@AnyThread
static int getBinderIdentityHashCode(@Nullable IInputMethodInvoker obj) {
if (obj == null) {
return 0;
}
return System.identityHashCode(obj.mTarget);
}
@NonNull
private final IInputMethod mTarget;
private IInputMethodInvoker(@NonNull IInputMethod target) {
mTarget = target;
}
@AnyThread
@NonNull
IBinder asBinder() {
return mTarget.asBinder();
}
@AnyThread
void initializeInternal(IBinder token, IInputMethodPrivilegedOperations privOps,
int configChanges, boolean stylusHwSupported) {
try {
mTarget.initializeInternal(token, privOps, configChanges, stylusHwSupported);
} catch (RemoteException e) {
logRemoteException(e);
}
}
@AnyThread
void onCreateInlineSuggestionsRequest(InlineSuggestionsRequestInfo requestInfo,
IInlineSuggestionsRequestCallback cb) {
try {
mTarget.onCreateInlineSuggestionsRequest(requestInfo, cb);
} catch (RemoteException e) {
logRemoteException(e);
}
}
@AnyThread
void bindInput(InputBinding binding) {
try {
mTarget.bindInput(binding);
} catch (RemoteException e) {
logRemoteException(e);
}
}
@AnyThread
void unbindInput() {
try {
mTarget.unbindInput();
} catch (RemoteException e) {
logRemoteException(e);
}
}
@AnyThread
void startInput(IBinder startInputToken, IInputContext inputContext, EditorInfo attribute,
boolean restarting) {
try {
mTarget.startInput(startInputToken, inputContext, attribute, restarting);
} catch (RemoteException e) {
logRemoteException(e);
}
}
@AnyThread
void createSession(InputChannel channel, IInputSessionCallback callback) {
try {
mTarget.createSession(channel, callback);
} catch (RemoteException e) {
logRemoteException(e);
}
}
@AnyThread
void setSessionEnabled(IInputMethodSession session, boolean enabled) {
try {
mTarget.setSessionEnabled(session, enabled);
} catch (RemoteException e) {
logRemoteException(e);
}
}
// TODO(b/192412909): Convert this back to void method
@AnyThread
boolean showSoftInput(IBinder showInputToken, int flags, ResultReceiver resultReceiver) {
try {
mTarget.showSoftInput(showInputToken, flags, resultReceiver);
} catch (RemoteException e) {
logRemoteException(e);
return false;
}
return true;
}
// TODO(b/192412909): Convert this back to void method
@AnyThread
boolean hideSoftInput(IBinder hideInputToken, int flags, ResultReceiver resultReceiver) {
try {
mTarget.hideSoftInput(hideInputToken, flags, resultReceiver);
} catch (RemoteException e) {
logRemoteException(e);
return false;
}
return true;
}
@AnyThread
void changeInputMethodSubtype(InputMethodSubtype subtype) {
try {
mTarget.changeInputMethodSubtype(subtype);
} catch (RemoteException e) {
logRemoteException(e);
}
}
@AnyThread
void canStartStylusHandwriting(int requestId) {
try {
mTarget.canStartStylusHandwriting(requestId);
} catch (RemoteException e) {
logRemoteException(e);
}
}
@AnyThread
void startStylusHandwriting(InputChannel channel, List<MotionEvent> events) {
try {
mTarget.startStylusHandwriting(channel, events);
} catch (RemoteException e) {
logRemoteException(e);
}
}
}

View File

@@ -75,7 +75,7 @@ final class InputMethodBindingController {
@GuardedBy("ImfLock.class") @Nullable private String mCurId;
@GuardedBy("ImfLock.class") @Nullable private String mSelectedMethodId;
@GuardedBy("ImfLock.class") @Nullable private Intent mCurIntent;
@GuardedBy("ImfLock.class") @Nullable private IInputMethod mCurMethod;
@GuardedBy("ImfLock.class") @Nullable private IInputMethodInvoker mCurMethod;
@GuardedBy("ImfLock.class") private int mCurMethodUid = Process.INVALID_UID;
@GuardedBy("ImfLock.class") private IBinder mCurToken;
@GuardedBy("ImfLock.class") private int mCurSeq;
@@ -241,7 +241,7 @@ final class InputMethodBindingController {
*/
@GuardedBy("ImfLock.class")
@Nullable
IInputMethod getCurMethod() {
IInputMethodInvoker getCurMethod() {
return mCurMethod;
}
@@ -298,7 +298,7 @@ final class InputMethodBindingController {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.onServiceConnected");
synchronized (ImfLock.class) {
if (mCurIntent != null && name.equals(mCurIntent.getComponent())) {
mCurMethod = IInputMethod.Stub.asInterface(service);
mCurMethod = IInputMethodInvoker.create(IInputMethod.Stub.asInterface(service));
updateCurrentMethodUid();
if (mCurToken == null) {
Slog.w(TAG, "Service connected without a token!");

View File

@@ -167,7 +167,6 @@ import com.android.internal.util.DumpUtils;
import com.android.internal.view.IInlineSuggestionsRequestCallback;
import com.android.internal.view.IInlineSuggestionsResponseCallback;
import com.android.internal.view.IInputContext;
import com.android.internal.view.IInputMethod;
import com.android.internal.view.IInputMethodClient;
import com.android.internal.view.IInputMethodManager;
import com.android.internal.view.IInputMethodSession;
@@ -329,7 +328,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
static class SessionState {
final ClientState client;
final IInputMethod method;
final IInputMethodInvoker method;
IInputMethodSession session;
InputChannel channel;
@@ -338,14 +337,14 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
public String toString() {
return "SessionState{uid " + client.uid + " pid " + client.pid
+ " method " + Integer.toHexString(
System.identityHashCode(method))
IInputMethodInvoker.getBinderIdentityHashCode(method))
+ " session " + Integer.toHexString(
System.identityHashCode(session))
+ " channel " + channel
+ "}";
}
SessionState(ClientState _client, IInputMethod _method,
SessionState(ClientState _client, IInputMethodInvoker _method,
IInputMethodSession _session, InputChannel _channel) {
client = _client;
method = _method;
@@ -613,7 +612,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
*/
@GuardedBy("ImfLock.class")
@Nullable
private IInputMethod getCurMethodLocked() {
private IInputMethodInvoker getCurMethodLocked() {
return mBindingController.getCurMethod();
}
@@ -692,8 +691,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
/**
* Internal state snapshot when
* {@link IInputMethod#startInput(IBinder, IInputContext, EditorInfo, boolean)} is about to be
* called.
* {@link com.android.internal.view.IInputMethod#startInput(IBinder, IInputContext, EditorInfo,
* boolean)} is about to be called.
*
* <p>Calling that IPC endpoint basically means that
* {@link InputMethodService#doStartInput(InputConnection, EditorInfo, boolean)} will be called
@@ -1951,18 +1950,14 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
InlineSuggestionsRequestInfo requestInfo, IInlineSuggestionsRequestCallback callback) {
final InputMethodInfo imi = mMethodMap.get(getSelectedMethodIdLocked());
try {
IInputMethod curMethod = getCurMethodLocked();
IInputMethodInvoker curMethod = getCurMethodLocked();
if (userId == mSettings.getCurrentUserId() && imi != null
&& imi.isInlineSuggestionsEnabled() && curMethod != null) {
final IInlineSuggestionsRequestCallback callbackImpl =
new InlineSuggestionsRequestCallbackDecorator(callback,
imi.getPackageName(), mCurTokenDisplayId, getCurTokenLocked(),
this);
try {
curMethod.onCreateInlineSuggestionsRequest(requestInfo, callbackImpl);
} catch (RemoteException e) {
Slog.w(TAG, "RemoteException calling onCreateInlineSuggestionsRequest()", e);
}
curMethod.onCreateInlineSuggestionsRequest(requestInfo, callbackImpl);
} else {
callback.onInlineSuggestionsUnsupported();
}
@@ -2190,13 +2185,9 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
mCurFocusedWindow, 0, null, SoftInputShowHideReason.HIDE_REMOVE_CLIENT);
if (mBoundToMethod) {
mBoundToMethod = false;
IInputMethod curMethod = getCurMethodLocked();
IInputMethodInvoker curMethod = getCurMethodLocked();
if (curMethod != null) {
try {
curMethod.unbindInput();
} catch (RemoteException e) {
// There is nothing interesting about the method dying.
}
curMethod.unbindInput();
}
}
mCurClient = null;
@@ -2230,13 +2221,9 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
+ mCurClient.client.asBinder());
if (mBoundToMethod) {
mBoundToMethod = false;
IInputMethod curMethod = getCurMethodLocked();
IInputMethodInvoker curMethod = getCurMethodLocked();
if (curMethod != null) {
try {
curMethod.unbindInput();
} catch (RemoteException e) {
// There is nothing interesting about the method dying.
}
curMethod.unbindInput();
}
}
@@ -2285,11 +2272,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
@NonNull
InputBindResult attachNewInputLocked(@StartInputReason int startInputReason, boolean initial) {
if (!mBoundToMethod) {
IInputMethod curMethod = getCurMethodLocked();
try {
curMethod.bindInput(mCurClient.binding);
} catch (RemoteException e) {
}
getCurMethodLocked().bindInput(mCurClient.binding);
mBoundToMethod = true;
}
@@ -2316,11 +2299,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
}
final SessionState session = mCurClient.curSession;
try {
setEnabledSessionLocked(session);
session.method.startInput(startInputToken, mCurInputContext, mCurAttribute, restarting);
} catch (RemoteException e) {
}
setEnabledSessionLocked(session);
session.method.startInput(startInputToken, mCurInputContext, mCurAttribute, restarting);
if (mShowRequested) {
if (DEBUG) Slog.v(TAG, "Attach new input asks to show input");
@@ -2514,18 +2494,14 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
}
@GuardedBy("ImfLock.class")
void initializeImeLocked(@NonNull IInputMethod inputMethod, @NonNull IBinder token,
void initializeImeLocked(@NonNull IInputMethodInvoker inputMethod, @NonNull IBinder token,
@android.content.pm.ActivityInfo.Config int configChanges, boolean supportStylusHw) {
if (DEBUG) {
Slog.v(TAG, "Sending attach of token: " + token + " for display: "
+ mCurTokenDisplayId);
}
try {
inputMethod.initializeInternal(token,
new InputMethodPrivilegedOperationsImpl(this, token), configChanges,
supportStylusHw);
} catch (RemoteException e) {
}
inputMethod.initializeInternal(token, new InputMethodPrivilegedOperationsImpl(this, token),
configChanges, supportStylusHw);
}
@AnyThread
@@ -2535,7 +2511,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
}
@BinderThread
void onSessionCreated(IInputMethod method, IInputMethodSession session, InputChannel channel) {
void onSessionCreated(IInputMethodInvoker method, IInputMethodSession session,
InputChannel channel) {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.onSessionCreated");
try {
synchronized (ImfLock.class) {
@@ -2545,7 +2522,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
channel.dispose();
return;
}
IInputMethod curMethod = getCurMethodLocked();
IInputMethodInvoker curMethod = getCurMethodLocked();
if (curMethod != null && method != null
&& curMethod.asBinder() == method.asBinder()) {
if (mCurClient != null) {
@@ -2609,7 +2586,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
cs.sessionRequested = true;
final IInputMethod curMethod = getCurMethodLocked();
final IInputMethodInvoker curMethod = getCurMethodLocked();
final IInputSessionCallback.Stub callback = new IInputSessionCallback.Stub() {
@Override
public void sessionCreated(IInputMethodSession session) {
@@ -2624,7 +2601,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
try {
curMethod.createSession(clientChannel, callback);
} catch (RemoteException e) {
} finally {
// Dispose the channel because the remote proxy will get its own copy when
// unparceled.
@@ -3012,14 +2988,10 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
}
if (newSubtype != oldSubtype) {
setSelectedInputMethodAndSubtypeLocked(info, subtypeId, true);
IInputMethod curMethod = getCurMethodLocked();
IInputMethodInvoker curMethod = getCurMethodLocked();
if (curMethod != null) {
try {
updateSystemUiLocked(mImeWindowVis, mBackDisposition);
curMethod.changeInputMethodSubtype(newSubtype);
} catch (RemoteException e) {
Slog.w(TAG, "Failed to call changeInputMethodSubtype");
}
updateSystemUiLocked(mImeWindowVis, mBackDisposition);
curMethod.changeInputMethodSubtype(newSubtype);
}
}
return;
@@ -3096,13 +3068,9 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
return;
}
if (DEBUG) Slog.v(TAG, "Client requesting Stylus Handwriting to be started");
final IInputMethod curMethod = getCurMethodLocked();
final IInputMethodInvoker curMethod = getCurMethodLocked();
if (curMethod != null) {
try {
curMethod.canStartStylusHandwriting(++mHwRequestId);
} catch (RemoteException e) {
Slog.w(TAG, "RemoteException calling canStartStylusHandwriting(): ", e);
}
curMethod.canStartStylusHandwriting(++mHwRequestId);
}
} finally {
Binder.restoreCallingIdentity(ident);
@@ -3153,21 +3121,20 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
}
mBindingController.setCurrentMethodVisible();
final IInputMethod curMethod = getCurMethodLocked();
final IInputMethodInvoker curMethod = getCurMethodLocked();
if (curMethod != null) {
// create a placeholder token for IMS so that IMS cannot inject windows into client app.
Binder showInputToken = new Binder();
mShowRequestWindowMap.put(showInputToken, windowToken);
final int showFlags = getImeShowFlagsLocked();
try {
if (DEBUG) {
Slog.v(TAG, "Calling " + curMethod + ".showSoftInput(" + showInputToken
+ ", " + showFlags + ", " + resultReceiver + ") for reason: "
+ InputMethodDebug.softInputDisplayReasonToString(reason));
}
curMethod.showSoftInput(showInputToken, showFlags, resultReceiver);
if (DEBUG) {
Slog.v(TAG, "Calling " + curMethod + ".showSoftInput(" + showInputToken
+ ", " + showFlags + ", " + resultReceiver + ") for reason: "
+ InputMethodDebug.softInputDisplayReasonToString(reason));
}
// TODO(b/192412909): Check if we can always call onShowHideSoftInputRequested() or not.
if (curMethod.showSoftInput(showInputToken, showFlags, resultReceiver)) {
onShowHideSoftInputRequested(true /* show */, windowToken, reason);
} catch (RemoteException e) {
}
mInputShown = true;
return true;
@@ -3236,7 +3203,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
// since Android Eclair. That's why we need to accept IMM#hideSoftInput() even when only
// IMMS#InputShown indicates that the software keyboard is shown.
// TODO: Clean up, IMMS#mInputShown, IMMS#mImeWindowVis and mShowRequested.
IInputMethod curMethod = getCurMethodLocked();
IInputMethodInvoker curMethod = getCurMethodLocked();
final boolean shouldHideSoftInput = (curMethod != null) && (mInputShown
|| (mImeWindowVis & InputMethodService.IME_ACTIVE) != 0);
boolean res;
@@ -3252,10 +3219,9 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
+ ", " + resultReceiver + ") for reason: "
+ InputMethodDebug.softInputDisplayReasonToString(reason));
}
try {
curMethod.hideSoftInput(hideInputToken, 0 /* flags */, resultReceiver);
// TODO(b/192412909): Check if we can always call onShowHideSoftInputRequested() or not.
if (curMethod.hideSoftInput(hideInputToken, 0 /* flags */, resultReceiver)) {
onShowHideSoftInputRequested(false /* show */, windowToken, reason);
} catch (RemoteException e) {
}
res = true;
} else {
@@ -4160,7 +4126,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
}
}
/** Called right after {@link IInputMethod#showSoftInput}. */
/** Called right after {@link com.android.internal.view.IInputMethod#showSoftInput}. */
@GuardedBy("ImfLock.class")
private void onShowHideSoftInputRequested(boolean show, IBinder requestToken,
@SoftInputShowHideReason int reason) {
@@ -4215,19 +4181,13 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
void setEnabledSessionLocked(SessionState session) {
if (mEnabledSession != session) {
if (mEnabledSession != null && mEnabledSession.session != null) {
try {
if (DEBUG) Slog.v(TAG, "Disabling: " + mEnabledSession);
mEnabledSession.method.setSessionEnabled(mEnabledSession.session, false);
} catch (RemoteException e) {
}
if (DEBUG) Slog.v(TAG, "Disabling: " + mEnabledSession);
mEnabledSession.method.setSessionEnabled(mEnabledSession.session, false);
}
mEnabledSession = session;
if (mEnabledSession != null && mEnabledSession.session != null) {
try {
if (DEBUG) Slog.v(TAG, "Enabling: " + mEnabledSession);
mEnabledSession.method.setSessionEnabled(mEnabledSession.session, true);
} catch (RemoteException e) {
}
if (DEBUG) Slog.v(TAG, "Enabling: " + mEnabledSession);
mEnabledSession.method.setSessionEnabled(mEnabledSession.session, true);
}
}
}
@@ -4394,12 +4354,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
return;
}
try {
// TODO: replace null with actual Channel, MotionEvents
getCurMethodLocked().startStylusHandwriting(null, null);
} catch (RemoteException e) {
Slog.w(TAG, "RemoteException calling startStylusHandwriting(): ", e);
}
// TODO: replace null with actual Channel, MotionEvents
getCurMethodLocked().startStylusHandwriting(null, null);
}
}
@@ -5132,7 +5088,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
@BinderThread
private void dumpAsStringNoCheck(FileDescriptor fd, PrintWriter pw, String[] args,
boolean isCritical) {
IInputMethod method;
IInputMethodInvoker method;
ClientState client;
ClientState focusedWindowClient;