来到新公司,负责了输入法的问题处理和一些输入法相关的功能开发,所以对输入法有了一点点了解,所以写了这篇文章来从系统的角度浅谈一下输入法。
输入法管理服务的整体框架
输入法的整件框架:
输入法管理服务InputMethodManagerService主要包括三个模块:
- 第一个是app应用进程:
此部分可以使用InputMethodManager类来发起显示输入法或隐藏输入法的请求,也可以配置输入法的一些属性。
- 第二个是输入法应用进程:
这个就是我们通常意义上说的输入法应用,其主是InputMethodService的一个实现类,如qq输入法,搜狗输入法。
- 第三个是系统SystemServer进程的InputMethodManagerService类:
这个才是输入法的管理核心类。
输入法应用–InputMethodService
我们先讨论输入法应用部分:
以android内置的输入法LatinIME为例,
先看LatinIME输入的AndroidManifest.xml对其定义:
<service android:name="LatinIME"
android:label="@string/english_ime_name"
android:permission="android.permission.BIND_INPUT_METHOD">
<intent-filter>
<action android:name="android.view.InputMethod" />
</intent-filter>
<meta-data android:name="android.view.im" android:resource="@xml/method" />
</service>
从这可以看出,LatinIME是一个service。
再看LatinIME类:
public class LatinIME extends InputMethodService implements KeyboardActionListener,
SuggestionStripView.Listener, SuggestionStripViewAccessor,
DictionaryFacilitator.DictionaryInitializationListener,
PermissionsManager.PermissionsResultCallback {
可以看出,输入法LatinIME是InputMethodService的实现类。
那InputMethodService是什么呢?
请看下面InputMethodService的类图:
InputMethodService的类图清楚的表示其是一个带Dialog的Service,所以,输入法应用可以简单的理解为一个带Dialog的Service。
InputMethodService类中的mInputView 对应类LatinIME.mInputView,而LatinIME.mInputView对应KeyboardSwitcher.onCreateInputView方法中的mCurrentInputView :
public View onCreateInputView(final boolean isHardwareAcceleratedDrawingEnabled) {
......
mCurrentInputView = (InputView)LayoutInflater.from(mThemeContext).inflate(
R.layout.input_view, null);
下面我们讲解app应用如何显示输入法界面,而InputMethodManagerService类是如何响应app的调用显示输入法。
app应用调用显示输入法和隐藏输入法
以我们在app应用中,点击一个输入框,显示输入法为例:
1.初始化InputMethodManager:
在InputMethodManager.InputMethodManager添加堆栈信息:
private InputMethodManager(IInputMethodManager service, int displayId, Looper looper) {
其打印信息:
android.view.inputmethod.InputMethodManager.<init>(InputMethodManager.java:955)
android.view.inputmethod.InputMethodManager.createRealInstance(InputMethodManager.java:904)
android.view.inputmethod.InputMethodManager.createInstance(InputMethodManager.java:892)
android.view.inputmethod.InputMethodManager.forContextInternal(InputMethodManager.java:988)
android.view.inputmethod.InputMethodManager.forContext(InputMethodManager.java:977)
android.app.SystemServiceRegistry$27.getService(SystemServiceRegistry.java:457)
android.app.SystemServiceRegistry$27.getService(SystemServiceRegistry.java:454)
android.app.SystemServiceRegistry.getSystemService(SystemServiceRegistry.java:1366)
android.app.ContextImpl.getSystemService(ContextImpl.java:1809)
android.content.ContextWrapper.getSystemService(ContextWrapper.java:752)
2.调用InputMethodManager.startInputInner接口
在InputMethodManager.startInputInner中添加堆栈信息:
boolean startInputInner(@StartInputReason int startInputReason,
@Nullable IBinder windowGainingFocus, @StartInputFlags int startInputFlags,
@SoftInputModeFlags int softInputMode, int windowFlags) {
......
try {
if (DEBUG) Log.v(TAG, "START INPUT: view=" + dumpViewInfo(view) + " ic="
+ ic + " tba=" + tba + " startInputFlags="
+ InputMethodDebug.startInputFlagsToString(startInputFlags));
final InputBindResult res = mService.startInputOrWindowGainedFocus(
startInputReason, mClient, windowGainingFocus, startInputFlags,
softInputMode, windowFlags, tba, servedContext, missingMethodFlags,
view.getContext().getApplicationInfo().targetSdkVersion);
......
其打印信息:
android.view.inputmethod.InputMethodManager.startInputInner(InputMethodManager.java:1772)
android.view.inputmethod.InputMethodManager.checkFocus(InputMethodManager.java:1920)
android.view.inputmethod.InputMethodManager.viewClicked(InputMethodManager.java:2204)
android.widget.TextView.viewClicked(TextView.java:12914)
android.widget.TextView.onTouchEvent(TextView.java:10915)
android.view.View.dispatchTouchEvent(View.java:13980)
android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3083)
android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2778)
android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3083)
android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2778)
android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3083)
android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2778)
android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3083)
android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2778)
android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3083)
android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2778)
android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3083)
android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2778)
com.android.internal.policy.DecorView.superDispatchTouchEvent(DecorView.java:481)
com.android.internal.policy.PhoneWindow.superDispatchTouchEvent(PhoneWindow.java:1854)
android.app.Activity.dispatchTouchEvent(Activity.java:4030)
androidx.appcompat.view.WindowCallbackWrapper.dispatchTouchEvent(WindowCallbackWrapper.java:69)
com.android.internal.policy.DecorView.dispatchTouchEvent(DecorView.java:439)
android.view.View.dispatchPointerEvent(View.java:14239)
android.view.ViewRootImpl$ViewPostImeInputStage.processPointerEvent(ViewRootImpl.java:5767)
android.view.ViewRootImpl$ViewPostImeInputStage.onProcess(ViewRootImpl.java:5564)
android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:5067)
android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:5120)
android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:5086)
android.view.ViewRootImpl$AsyncInputStage.forward(ViewRootImpl.java:5226)
android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:5094)
android.view.ViewRootImpl$AsyncInputStage.apply(ViewRootImpl.java:5283)
android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:5067)
android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:5120)
android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:5086)
android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:5094)
android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:5067)
android.view.ViewRootImpl.deliverInputEvent(ViewRootImpl.java:7796)
android.view.ViewRootImpl.doProcessInputEvents(ViewRootImpl.java:7765)
android.view.ViewRootImpl.enqueueInputEvent(ViewRootImpl.java:7726)
android.view.ViewRootImpl$WindowInputEventReceiver.onInputEvent(ViewRootImpl.java:7921)
android.view.InputEventReceiver.dispatchInputEvent(InputEventReceiver.java:189)
android.os.MessageQueue.nativePollOnce(Native Method)
android.os.MessageQueue.next(MessageQueue.java:336)
android.os.Looper.loop(Looper.java:181)
android.app.ActivityThread.main(ActivityThread.java:7574)
java.lang.reflect.Method.invoke(Native Method)
com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
com.android.internal.os.ZygoteInit.main(ZygoteInit.java:935)
我们简单的看一下几个核心的接口:
TextView.onTouchEvent
public boolean onTouchEvent(MotionEvent event) {
......
if (touchIsFinished && (isTextEditable() || textIsSelectable)) {
// Show the IME, except when selecting in read-only text.
final InputMethodManager imm = getInputMethodManager();
viewClicked(imm);//确认view click
if (isTextEditable() && mEditor.mShowSoftInputOnFocus && imm != null) {
imm.showSoftInput(this, 0);//显示输入法
}
......
TextView.viewClicked
protected void viewClicked(InputMethodManager imm) {
if (imm != null) {
imm.viewClicked(this);//调用imm.viewClicked
}
}
InputMethodManager.viewClicked
public void viewClicked(View view) {
.....
final boolean focusChanged = mServedView != mNextServedView;
checkFocus();//调用checkFocus
InputMethodManager.checkFocus
public void checkFocus() {
if (checkFocusNoStartInput(false)) {
startInputInner(StartInputReason.CHECK_FOCUS, null, 0, 0, 0);//调用startInputInner
}
}
InputMethodManager.startInputInner
boolean startInputInner(@StartInputReason int startInputReason,
@Nullable IBinder windowGainingFocus, @StartInputFlags int startInputFlags,
@SoftInputModeFlags int softInputMode, int windowFlags) {
......
try {
if (DEBUG) Log.v(TAG, "START INPUT: view=" + dumpViewInfo(view) + " ic="
+ ic + " tba=" + tba + " startInputFlags="
+ InputMethodDebug.startInputFlagsToString(startInputFlags));
final InputBindResult res = mService.startInputOrWindowGainedFocus(
startInputReason, mClient, windowGainingFocus, startInputFlags,
softInputMode, windowFlags, tba, servedContext, missingMethodFlags,
view.getContext().getApplicationInfo().targetSdkVersion);//调用mService.startInputOrWindowGainedFocus
......
我们可以从上面的日志信息明确看到我们点击输入框,系统如何从:
TextView.onTouchEvent----InputMethodManager.viewClicked----InputMethodManager.startInputInner----InputMethodManagerService.startInputOrWindowGainedFocus
下面就是其类图:
3.InputMethodManagerService类发送消息MSG_START_INPUT
在InputMethodManagerService.handleMessage中添加堆栈信息:
public boolean handleMessage(Message msg) {
Slog.v(TAG, "handleMessage msg.what:" + msg.what);
在接上面InputMethodManagerService.startInputOrWindowGainedFocus接口调用后,打印日志信息:
com.android.server.inputmethod.InputMethodManagerService.handleMessage(InputMethodManagerService.java:3810)
com.android.server.inputmethod.InputMethodManagerService.executeOrSendMessage(InputMethodManagerService.java:2013)
com.android.server.inputmethod.InputMethodManagerService.attachNewInputLocked(InputMethodManagerService.java:2080)
com.android.server.inputmethod.InputMethodManagerService.startInputUncheckedLocked(InputMethodManagerService.java:2208)
com.android.server.inputmethod.InputMethodManagerService.startInputOrWindowGainedFocusInternalLocked(InputMethodManagerService.java:3171)
com.android.server.inputmethod.InputMethodManagerService.startInputOrWindowGainedFocus(InputMethodManagerService.java:3076)
com.android.internal.view.IInputMethodManager$Stub.onTransact(IInputMethodManager.java:331)
com.android.server.inputmethod.InputMethodManagerService.onTransact(InputMethodManagerService.java:1636)
android.os.Binder.execTransactInternal(Binder.java:1021)
android.os.Binder.execTransact(Binder.java:994)
handleMessage msg.what:2000
通过上面信息,我们知道其调用过程:
先查看InputMethodManagerService.startInputOrWindowGainedFocus:
public InputBindResult startInputOrWindowGainedFocus(
@StartInputReason int startInputReason, IInputMethodClient client, IBinder windowToken,
@StartInputFlags int startInputFlags, @SoftInputModeFlags int softInputMode,
int windowFlags, @Nullable EditorInfo attribute, IInputContext inputContext,
@MissingMethodFlags int missingMethods, int unverifiedTargetSdkVersion) {
....
final InputBindResult result;
synchronized (mMethodMap) {
final long ident = Binder.clearCallingIdentity();
try {
result = startInputOrWindowGainedFocusInternalLocked(startInputReason, client,
windowToken, startInputFlags, softInputMode, windowFlags, attribute,
inputContext, missingMethods, unverifiedTargetSdkVersion, userId);//调用startInputOrWindowGainedFocusInternalLocked
InputMethodManagerService.startInputOrWindowGainedFocusInternalLocked
private InputBindResult startInputOrWindowGainedFocusInternalLocked(
@StartInputReason int startInputReason, IInputMethodClient client,
@NonNull IBinder windowToken, @StartInputFlags int startInputFlags,
@SoftInputModeFlags int softInputMode, int windowFlags, EditorInfo attribute,
IInputContext inputContext, @MissingMethodFlags int missingMethods,
int unverifiedTargetSdkVersion, @UserIdInt int userId) {
......
if (mCurFocusedWindow == windowToken) {
if (DEBUG) {
Slog.w(TAG, "Window already focused, ignoring focus gain of: " + client
+ " attribute=" + attribute + ", token = " + windowToken);
}
if (attribute != null) {
return startInputUncheckedLocked(cs, inputContext, missingMethods,
attribute, startInputFlags, startInputReason);//调用startInputUncheckedLocked
InputMethodManagerService.startInputUncheckedLocked
InputBindResult startInputUncheckedLocked(@NonNull ClientState cs, IInputContext inputContext,
@MissingMethodFlags int missingMethods, @NonNull EditorInfo attribute,
@StartInputFlags int startInputFlags, @StartInputReason int startInputReason) {
......
if (mCurId != null && mCurId.equals(mCurMethodId)
&& displayIdToShowIme == mCurTokenDisplayId) {
if (cs.curSession != null) {
// Fast case: if we are already connected to the input method,
// then just return it.
return attachNewInputLocked(startInputReason,
(startInputFlags & StartInputFlags.INITIAL_CONNECTION) != 0);//调用attachNewInputLocked
InputMethodManagerService.attachNewInputLocked
InputBindResult attachNewInputLocked(@StartInputReason int startInputReason, boolean initial) {
......
final SessionState session = mCurClient.curSession;
executeOrSendMessage(session.method, mCaller.obtainMessageIIOOOO(
MSG_START_INPUT, mCurInputContextMissingMethods, initial ? 0 : 1 /* restarting */,
startInputToken, session, mCurInputContext, mCurAttribute));//发送MSG_START_INPUT消息
发送MSG_START_INPUT消息,对应接受到的消息为2000
handleMessage msg.what:2000
MSG_START_INPUT定义为:
static final int MSG_START_INPUT = 2000;
接受到的消息为2000后,对应调用:
public boolean handleMessage(Message msg) {
......
switch (msg.what) {
......
case MSG_START_INPUT: {
final int missingMethods = msg.arg1;
final boolean restarting = msg.arg2 != 0;
args = (SomeArgs) msg.obj;
final IBinder startInputToken = (IBinder) args.arg1;
final SessionState session = (SessionState) args.arg2;
final IInputContext inputContext = (IInputContext) args.arg3;
final EditorInfo editorInfo = (EditorInfo) args.arg4;
try {
setEnabledSessionInMainThread(session);
session.method.startInput(startInputToken, inputContext, missingMethods,
editorInfo, restarting, session.client.shouldPreRenderIme);//调用method.startInput方法
} catch (RemoteException e) {
}
args.recycle();
return true;
}
对应调用session.method.startInput为:
InputMethodService.InputMethodImpl.startInput方法:
public void startInput(InputConnection ic, EditorInfo attribute) {
if (DEBUG) Log.v(TAG, "startInput(): editor=" + attribute);
doStartInput(ic, attribute, false);
}
4.InputMethodManager.showSoftInput
在InputMethodManager.showSoftInput添加日志信息:
public boolean showSoftInput(View view, int flags, ResultReceiver resultReceiver) {
......
try {
return mService.showSoftInput(mClient, flags, resultReceiver);
打印日志信息如下:
android.view.inputmethod.InputMethodManager.showSoftInput(InputMethodManager.java:1444)
android.view.inputmethod.InputMethodManager.showSoftInput(InputMethodManager.java:1368)
android.widget.TextView.onTouchEvent(TextView.java:10917)
查看TextView.onTouchEvent:
public boolean onTouchEvent(MotionEvent event) {
......
viewClicked(imm);
if (isTextEditable() && mEditor.mShowSoftInputOnFocus && imm != null) {
imm.showSoftInput(this, 0);
}
InputMethodManager.showSoftInput
public boolean showSoftInput(View view, int flags, ResultReceiver resultReceiver) {
......
try {
return mService.showSoftInput(mClient, flags, resultReceiver);//在此调用mService.showSoftInput
5.InputMethodManagerService类发送消息MSG_SHOW_SOFT_INPUT
输出InputMethodManagerService调用日志信息:
com.android.server.inputmethod.InputMethodManagerService.handleMessage(InputMethodManagerService.java:3810)
com.android.server.inputmethod.InputMethodManagerService.executeOrSendMessage(InputMethodManagerService.java:2013)
com.android.server.inputmethod.InputMethodManagerService.showCurrentInputLocked(InputMethodManagerService.java:2890)
com.android.server.inputmethod.InputMethodManagerService.showSoftInput(InputMethodManagerService.java:2862)
handler处理的消息为:1020
handleMessage msg.what:1020
其1020消息的定义为:
static final int MSG_SHOW_SOFT_INPUT = 1020;
我们查看一下上面的核心关键代码:
InputMethodManagerService.showSoftInput
public boolean showSoftInput(IInputMethodClient client, int flags,
ResultReceiver resultReceiver) {
....
return showCurrentInputLocked(flags, resultReceiver);
InputMethodManagerService.showCurrentInputLocked
boolean showCurrentInputLocked(int flags, ResultReceiver resultReceiver) {
......
if (mCurMethod != null) {
if (DEBUG) Slog.d(TAG, "showCurrentInputLocked: mCurToken=" + mCurToken);
executeOrSendMessage(mCurMethod, mCaller.obtainMessageIOO(
MSG_SHOW_SOFT_INPUT, getImeShowFlags(), mCurMethod,
resultReceiver));//发送MSG_SHOW_SOFT_INPUT消息
public boolean handleMessage(Message msg) {
......
switch (msg.what) {
......
case MSG_SHOW_SOFT_INPUT:
args = (SomeArgs)msg.obj;
try {
if (DEBUG) Slog.v(TAG, "Calling " + args.arg1 + ".showSoftInput("
+ msg.arg1 + ", " + args.arg2 + ")");
((IInputMethod)args.arg1).showSoftInput(msg.arg1, (ResultReceiver)args.arg2);
} catch (RemoteException e) {
}
args.recycle();
return true;
调用InputMethodService.InputMethodImpl.showSoftInput:
public void showSoftInput(int flags, ResultReceiver resultReceiver) {
.......
showWindow(true);
InputMethodService.showWindow
public void showWindow(boolean showInput) {
......
mWindow.show();
整个点击输入框显示输入法的流程图为:
在搜狗输入法上隐藏输入法
在InputMethodPrivilegedOperations.hideMySoftInput中添加堆栈信息:
public void hideMySoftInput(int flags) {
app应用层打印出日志信息:
com.android.internal.inputmethod.InputMethodPrivilegedOperations.hideMySoftInput(InputMethodPrivilegedOperations.java:249)
android.inputmethodservice.InputMethodService.requestHideSelf(InputMethodService.java:2279)
com.sohu.inputmethod.sogou.SogouIME.requestHideSelf(SogouSource:586)
我们查看一下关键代码信息:
InputMethodService.requestHideSelf
public void requestHideSelf(int flags) {
mPrivOps.hideMySoftInput(flags);
}
InputMethodPrivilegedOperations.hideMySoftInput
public void hideMySoftInput(int flags) {
final IInputMethodPrivilegedOperations ops = mOps.getAndWarnIfNull();
if (ops == null) {
return;
}
try {
ops.hideMySoftInput(flags);//调用ops.hideMySoftInput
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
system server 对应打印出日志信息:
com.android.server.inputmethod.InputMethodManagerService.handleMessage(InputMethodManagerService.java:3810)
com.android.server.inputmethod.InputMethodManagerService.executeOrSendMessage(InputMethodManagerService.java:2013)
com.android.server.inputmethod.InputMethodManagerService.hideCurrentInputLocked(InputMethodManagerService.java:3001)
com.android.server.inputmethod.InputMethodManagerService.hideMySoftInput(InputMethodManagerService.java:3762)
com.android.server.inputmethod.InputMethodManagerService.access$3100(InputMethodManagerService.java:192)
com.android.server.inputmethod.InputMethodManagerService$InputMethodPrivilegedOperationsImpl.hideMySoftInput(InputMethodManagerService.java:5365)
我们查看关键代码信息:
InputMethodManagerService$InputMethodPrivilegedOperationsImpl.hideMySoftInput
public void hideMySoftInput(int flags) {
mImms.hideMySoftInput(mToken, flags);
}
InputMethodManagerService.hideMySoftInput
private void hideMySoftInput(@NonNull IBinder token, int flags) {
synchronized (mMethodMap) {
if (!calledWithValidTokenLocked(token)) {
return;
}
long ident = Binder.clearCallingIdentity();
try {
hideCurrentInputLocked(flags, null);
InputMethodManagerService.hideCurrentInputLocked
boolean hideCurrentInputLocked(int flags, ResultReceiver resultReceiver) {
......
executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
MSG_HIDE_SOFT_INPUT, mCurMethod, resultReceiver));
InputMethodManagerService.handleMessage
public boolean handleMessage(Message msg) {
......
case MSG_HIDE_SOFT_INPUT:
args = (SomeArgs)msg.obj;
try {
if (DEBUG) Slog.v(TAG, "Calling " + args.arg1 + ".hideSoftInput(0, "
+ args.arg2 + ")");
((IInputMethod)args.arg1).hideSoftInput(0, (ResultReceiver)args.arg2);
} catch (RemoteException e) {
}
args.recycle();
return true;
InputMethodService.hideSoftInput
public void hideSoftInput(int flags, ResultReceiver resultReceiver) {
......
mShowInputFlags = 0;
mShowInputRequested = false;
doHideWindow();
InputMethodService.doHideWindow
private void doHideWindow() {
setImeWindowStatus(0, mBackDisposition);
hideWindow();
}
InputMethodService.hideWindow
public void hideWindow() {
if (DEBUG) Log.v(TAG, "CALL: hideWindow");
mIsPreRendered = false;
mWindowVisible = false;
finishViews(false /* finishingInput */);
if (mDecorViewVisible) {
mWindow.hide();
mDecorViewVisible = false;
onWindowHidden();
mDecorViewWasVisible = false;
}
updateFullscreenMode();
}
我们可以得出完整的隐藏输入法的流程图:
输入法的日志开关
事实上,我们平时分析输入法的问题时,需要打开输入法的日志开关,才能更好的分析定位问题
frameworks\base\services\core\java\com\android\server\inputmethod\InputMethodManagerService.java
static final boolean DEBUG = false;
frameworks\base\services\core\java\com\android\server\wm\WindowManagerDebugConfig.java
static final boolean DEBUG_INPUT_METHOD = false;
frameworks\base\core\java\android\view\inputmethod\InputMethodManager.java
static final boolean DEBUG = false;
frameworks\base\core\java\android\inputmethodservice\InputMethodService.java
static final boolean DEBUG = false;
frameworks\base\services\core\java\com\android\server\inputmethod\InputMethodUtils.java
public static final boolean DEBUG = false;
frameworks\base\services\core\java\com\android\server\inputmethod\InputMethodSubtypeSwitchingController.java
private static final boolean DEBUG = false;
frameworks\base\core\java\android\inputmethodservice\SoftInputWindow.java
private static final boolean DEBUG = true;//false;
输入法的dump信息
adb shell dumpsys input_method
参考资料
1.Android输入法框架系统(上)
https://blog.csdn.net/ITleaks/article/details/27398453
2.Android输入法框架系统(下)
https://blog.csdn.net/ITleaks/article/details/27480403
3.InputMethodManager
https://developer.android.google.cn/reference/android/view/inputmethod/InputMethodManager?hl=en