Merge "Introduce IInputMethodInvoker"

This commit is contained in:
Yohei Yukawa
2022-01-24 18:37:38 +00:00
committed by Android (Google) Code Review
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;