Android输入法IMMS服务启动流程(5)(onUnlock
目录
1 user状态切换
2 onUnlockUser过程流程图
3 onUnlockUser
4 buildInputMethodListLocked
5 updateInputMethodsFromSettingsLocked
user状态切换
手机重启时,用户空间状态切换顺序:
// User is first coming up.
public final static int STATE_BOOTING = 0;
// User is in the locked state.
public final static int STATE_RUNNING_LOCKED = 1;
// User is in the unlocking state.
public final static int STATE_RUNNING_UNLOCKING = 2;
// User is in the running state.
public final static int STATE_RUNNING_UNLOCKED = 3;
用户的各个状态切换实在UserController完成的;
UNLOCKED状态是用户空间的正常运行的状态,到用户空间切到此状态时,会回调系统中各个服务的onUnlockUser方法;
因此我们从InputMethodManagerService的onUnlockUser方法看起
onUnlockUser过程流程图:
onUnlockUser.pngonUnlockUser
void onUnlockUser(@UserIdInt int userId) {
synchronized(mMethodMap) {
final int currentUserId = mSettings.getCurrentUserId();
if (DEBUG) {
Slog.d(TAG, "onUnlockUser: userId=" + userId + " curUserId=" + currentUserId);
}
if (userId != currentUserId) {
return;
}
mSettings.switchCurrentUser(currentUserId, !mSystemReady);
if (mSystemReady) {
// We need to rebuild IMEs.
buildInputMethodListLocked(false /* resetDefaultEnabledIme */);
updateInputMethodsFromSettingsLocked(true /* enabledChanged */);
}
}
}
buildInputMethodListLocked
@GuardedBy("mMethodMap")
void buildInputMethodListLocked(boolean resetDefaultEnabledIme) {
....
//清空输入相关列表和映射表
mMethodList.clear();
mMethodMap.clear();
mMethodMapUpdateCount++;
mMyPackageMonitor.clearKnownImePackageNamesLocked();
// Use for queryIntentServicesAsUser
final PackageManager pm = mContext.getPackageManager();
// Note: We do not specify PackageManager.MATCH_ENCRYPTION_* flags here because the default
// behavior of PackageManager is exactly what we want. It by default picks up appropriate
// services depending on the unlock state for the specified user.
final List<ResolveInfo> services = pm.queryIntentServicesAsUser(
new Intent(InputMethod.SERVICE_INTERFACE),
getComponentMatchingFlags(PackageManager.GET_META_DATA
| PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS),
mSettings.getCurrentUserId());
//获取系统中注册的输入法服务信息
Slog.v(TAG, "services: " + services);
final HashMap<String, List<InputMethodSubtype>> additionalSubtypeMap =
mFileManager.getAllAdditionalInputMethodSubtypes();
//根据获取的服务信息,解析InputMethodInfo信息,存储在mMethodList和mMethodMap
for (int i = 0; i < services.size(); ++i) {
ResolveInfo ri = services.get(i);
ServiceInfo si = ri.serviceInfo;
final String imeId = InputMethodInfo.computeId(ri);
if (!android.Manifest.permission.BIND_INPUT_METHOD.equals(si.permission)) {
Slog.w(TAG, "Skipping input method " + imeId
+ ": it does not require the permission "
+ android.Manifest.permission.BIND_INPUT_METHOD);
continue;
}
if (DEBUG) Slog.d(TAG, "Checking " + imeId);
final List<InputMethodSubtype> additionalSubtypes = additionalSubtypeMap.get(imeId);
try {
InputMethodInfo p = new InputMethodInfo(mContext, ri, additionalSubtypes);
mMethodList.add(p);
final String id = p.getId();
mMethodMap.put(id, p);
if (DEBUG) {
Slog.d(TAG, "Found an input method " + p);
}
} catch (Exception e) {
Slog.wtf(TAG, "Unable to load input method " + imeId, e);
}
}
// Construct the set of possible IME packages for onPackageChanged() to avoid false
// negatives when the package state remains to be the same but only the component state is
// changed.
{
// Here we intentionally use PackageManager.MATCH_DISABLED_COMPONENTS since the purpose
// of this query is to avoid false negatives. PackageManager.MATCH_ALL could be more
// conservative, but it seems we cannot use it for now (Issue 35176630).
//根据获取的服务信息,初始化包监测器mMyPackageMonitor
final List<ResolveInfo> allInputMethodServices = pm.queryIntentServicesAsUser(
new Intent(InputMethod.SERVICE_INTERFACE),
getComponentMatchingFlags(PackageManager.MATCH_DISABLED_COMPONENTS),
mSettings.getCurrentUserId());
Slog.v(TAG, "allInputMethodServices: " + allInputMethodServices);
final int N = allInputMethodServices.size();
for (int i = 0; i < N; ++i) {
final ServiceInfo si = allInputMethodServices.get(i).serviceInfo;
if (android.Manifest.permission.BIND_INPUT_METHOD.equals(si.permission)) {
mMyPackageMonitor.addKnownImePackageNameLocked(si.packageName);
}
}
}
boolean reenableMinimumNonAuxSystemImes = false;
// TODO: The following code should find better place to live.
if (!resetDefaultEnabledIme) {
boolean enabledImeFound = false;
boolean enabledNonAuxImeFound = false;
final List<InputMethodInfo> enabledImes = mSettings.getEnabledInputMethodListLocked();
final int N = enabledImes.size();
for (int i = 0; i < N; ++i) {
final InputMethodInfo imi = enabledImes.get(i);
if (mMethodList.contains(imi)) {
enabledImeFound = true;
if (!imi.isAuxiliaryIme()) {
enabledNonAuxImeFound = true;
break;
}
}
}
if (!enabledImeFound) {
if (DEBUG) {
Slog.i(TAG, "All the enabled IMEs are gone. Reset default enabled IMEs.");
}
resetDefaultEnabledIme = true;
resetSelectedInputMethodAndSubtypeLocked("");
} else if (!enabledNonAuxImeFound) {
if (DEBUG) {
Slog.i(TAG, "All the enabled non-Aux IMEs are gone. Do partial reset.");
}
reenableMinimumNonAuxSystemImes = true;
}
}//end if
if (resetDefaultEnabledIme || reenableMinimumNonAuxSystemImes) {
final ArrayList<InputMethodInfo> defaultEnabledIme =
InputMethodUtils.getDefaultEnabledImes(mContext, mMethodList,
reenableMinimumNonAuxSystemImes);
final int N = defaultEnabledIme.size();
for (int i = 0; i < N; ++i) {
final InputMethodInfo imi = defaultEnabledIme.get(i);
if (DEBUG) {
Slog.d(TAG, "--- enable ime = " + imi);
}
setInputMethodEnabledLocked(imi.getId(), true);
}
}
//com.sohu.inputmethod.sogou/.SogouIME
final String defaultImiId = mSettings.getSelectedInputMethod();
Slog.w(TAG, "defaultImiId:"+defaultImiId);
if (!TextUtils.isEmpty(defaultImiId)) {
if (!mMethodMap.containsKey(defaultImiId)) {//mMethodMap已经包换了搜狗输入法,因此去执行setInputMethodEnabledLocked
Slog.w(TAG, "Default IME is uninstalled. Choose new default IME.");
if (chooseNewDefaultIMELocked()) {
updateInputMethodsFromSettingsLocked(true);
}
} else {
// Double check that the default IME is certainly enabled.
setInputMethodEnabledLocked(defaultImiId, true);
}
}
// Here is not the perfect place to reset the switching controller. Ideally
// mSwitchingController and mSettings should be able to share the same state.
// TODO: Make sure that mSwitchingController and mSettings are sharing the
// the same enabled IMEs list.
mSwitchingController.resetCircularListLocked(mContext);
}
主要流程:
- 清空mMethodList,mMethodMap和输入法应用监测器mMyPackageMonitor
- 获取系统中注册的输入法服务信息,根据获取的信息初始化mMethodList,mMethodMap和输入法应用监测器mMyPackageMonitor
[ResolveInfo{71e33c6 com.iflytek.inputmethod/.FlyIME p=100 m=0x108000},
ResolveInfo{cc68f87 com.sohu.inputmethod.sogou/.SogouIME p=100 m=0x108000},
ResolveInfo{9b06eb4 com.android.inputmethod.latin/.LatinIME m=0x108000},
ResolveInfo{5122add com.bigtime.inputmethod.security/.SecuritySoftKeyboard m=0x108000},
ResolveInfo{5b9c652 com.qihoo.appstore/com.qihoo360.mobilesafe.pcinput.HandoffIme m=0x108000},
ResolveInfo{ff29d23 com.UCMobile/com.pp.assistant.cockroach.service.CockroachService m=0x108000}]
- 从settingprovider获取默认的输入法,因为此时InputMethodUtils$mCopyOnWrite = true,因此此时获取的是系统中实际包含的所有的输入应用注册的输入法服务
- 调用setInputMethodEnabledLocked,将搜狗输入法补充到可用输入列表中(对应settingprovider的enabled_input_methods)
完成对mMethodList和mMethodMap的数据初始化;
检查当前默认的输入法(LatinIME)服务是否存在,很明显,经过systemrunning过程以后,
mMethodMap已经包含了LatinIME,因此不会重复执行选择和设定另外输入法为默认输入法的操作
updateInputMethodsFromSettingsLocked
void updateInputMethodsFromSettingsLocked(boolean enabledMayChange) {
if (enabledMayChange) {
List<InputMethodInfo> enabled = mSettings.getEnabledInputMethodListLocked();
for (int i=0; i<enabled.size(); i++) {
// We allow the user to select "disabled until used" apps, so if they
// are enabling one of those here we now need to make it enabled.
InputMethodInfo imm = enabled.get(i);
try {
ApplicationInfo ai = mIPackageManager.getApplicationInfo(imm.getPackageName(),
PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS,
mSettings.getCurrentUserId());
if (ai != null && ai.enabledSetting
== PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED) {
if (DEBUG) {
Slog.d(TAG, "Update state(" + imm.getId()
+ "): DISABLED_UNTIL_USED -> DEFAULT");
}
mIPackageManager.setApplicationEnabledSetting(imm.getPackageName(),
PackageManager.COMPONENT_ENABLED_STATE_DEFAULT,
PackageManager.DONT_KILL_APP, mSettings.getCurrentUserId(),
mContext.getBasePackageName());
}
} catch (RemoteException e) {
}
}
}
// We are assuming that whoever is changing DEFAULT_INPUT_METHOD and
// ENABLED_INPUT_METHODS is taking care of keeping them correctly in
// sync, so we will never have a DEFAULT_INPUT_METHOD that is not
// enabled.
//搜狗输入法发
String id = mSettings.getSelectedInputMethod();
// There is no input method selected, try to choose new applicable input method.
if (TextUtils.isEmpty(id) && chooseNewDefaultIMELocked()) {
id = mSettings.getSelectedInputMethod();
}
if (!TextUtils.isEmpty(id)) {
try {
setInputMethodLocked(id, mSettings.getSelectedInputMethodSubtypeId(id));
} catch (IllegalArgumentException e) {
Slog.w(TAG, "Unknown input method from prefs: " + id, e);
resetCurrentMethodAndClient(InputMethodClient.UNBIND_REASON_SWITCH_IME_FAILED);
}
mShortcutInputMethodsAndSubtypes.clear();
} else {
// There is no longer an input method set, so stop any current one.
resetCurrentMethodAndClient(InputMethodClient.UNBIND_REASON_NO_IME);
}
// Here is not the perfect place to reset the switching controller. Ideally
// mSwitchingController and mSettings should be able to share the same state.
// TODO: Make sure that mSwitchingController and mSettings are sharing the
// the same enabled IMEs list.
mSwitchingController.resetCircularListLocked(mContext);
}
/* package */ void setInputMethodLocked(String id, int subtypeId) {
InputMethodInfo info = mMethodMap.get(id);
if (info == null) {
throw new IllegalArgumentException("Unknown id: " + id);
}
...
// Changing to a different IME.
final long ident = Binder.clearCallingIdentity();
try {
// Set a subtype to this input method.
// subtypeId the name of a subtype which will be set.
setSelectedInputMethodAndSubtypeLocked(info, subtypeId, false);
// mCurMethodId should be updated after setSelectedInputMethodAndSubtypeLocked()
// because mCurMethodId is stored as a history in
// setSelectedInputMethodAndSubtypeLocked().
mCurMethodId = id;
if (LocalServices.getService(ActivityManagerInternal.class).isSystemReady()) {
Intent intent = new Intent(Intent.ACTION_INPUT_METHOD_CHANGED);
intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
intent.putExtra("input_method_id", id);
mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT);
}
unbindCurrentClientLocked(InputMethodClient.UNBIND_REASON_SWITCH_IME);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
主要流程:
- 遍历可用输入法列表,并将设置为android:default = false,并且设置了GET_DISABLED_UNTIL_USED_COMPONENTS标识的输入法,置位可用
- 调用setInputMethodLocked,将mCurMethodId = com.sohu.inputmethod.sogou/.SogouIME,并发送ACTION_INPUT_METHOD_CHANGED广播
至此,重启手机过程,输入法设置为用户默认设置的输入法的流程解锁了。
重启过程中,有关输入法切换的日志见文章