Android 9.0 默认输入法的设置流程分析

Android 输入法设置文章

  Android 9.0 默认输入法的设置流程分析

  Android 9.0 添加预置第三方输入法/设置默认输入法(软键盘)

 

前言

在上一篇文章  Android 9.0 添加预置第三方输入法/设置默认输入法(软键盘)    中我们可以通过设置enabled_input_methods和default_input_method两个key-value的值来显示的指定可选的输入法及默认输入法。

但是,查看Android原生代码,并没任何地方显示的设置这两个值,但是当开机后,我们去console先查看,这两个值却被设置为了google原生输入法,那这两个值是在哪里设置的呢?本篇将简单介绍

设置流程分析

1.  Android系统开机后,当ActivityManagerService及PackageManagerService都ready后,systemserver会回调到InputMethodManagerService::systemRunning()方法//aosp.opersys.com/xref/android-9.0.0_r61/xref/frameworks/base/services/core/java/com/android/server/InputMethodManagerService.java#1508

 

2. systemRunning()方法中会去设置一些初始参数,并依次调用buildInputMethodListLocked和resetDefaultImeLocked

public void systemRunning(StatusBarManagerService statusBar) {
    synchronized (mMethodMap) {
        ....
        final String defaultImiId = mSettings.getSelectedInputMethod(); // 获取默认输入法,第一次开机时应该是空
        final boolean imeSelectedOnBoot = !TextUtils.isEmpty(defaultImiId);
        buildInputMethodListLocked(!imeSelectedOnBoot /* resetDefaultEnabledIme */);// 传递参数resetDefaultEnabledIme=true
        resetDefaultImeLocked(mContext);
        updateFromSettingsLocked(true);
        ....
    }
}

 

3. 接下来我们来看一下buildInputMethodListLocked方法,部分源码如下:

//aosp.opersys.com/xref/android-9.0.0_r61/xref/frameworks/base/services/core/java/com/android/server/InputMethodManagerService.java#3616

点击查看代码

void buildInputMethodListLocked(boolean resetDefaultEnabledIme) {
    if (DEBUG) {
        Slog.d(TAG, "--- re-buildInputMethodList reset = " + resetDefaultEnabledIme
                + " \n ------ caller=" + Debug.getCallers(10));
    }
    if (!mSystemReady) {
        Slog.e(TAG, "buildInputMethodListLocked is not allowed until system is ready");
        return;
    }
    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());

    final HashMap<String, List<InputMethodSubtype>> additionalSubtypeMap =
            mFileManager.getAllAdditionalInputMethodSubtypes();
    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).
        final List<ResolveInfo> allInputMethodServices = pm.queryIntentServicesAsUser(
                new Intent(InputMethod.SERVICE_INTERFACE),
                getComponentMatchingFlags(PackageManager.MATCH_DISABLED_COMPONENTS),
                mSettings.getCurrentUserId());
        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;
    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);
        }
    }

}

把代码处理流程大概分两个阶段:

第一阶段:透过PackageManager去检索已安装的输入法app,构建一个List:mMethodList

第二阶段:将上一步骤中检索的的输入法做enable ime处理,此时调用到了setInputMethodEnabledLocked(imi.getId(), true)

 

4.  再来看看setInputMethodEnabledLocked的内容:这个方法比较简单,调用mSettings.appendAndPutEnabledInputMethodLocked(id, false)去做设置

//aosp.opersys.com/xref/android-9.0.0_r61/xref/frameworks/base/services/core/java/com/android/server/InputMethodManagerService.java#3987

点击查看代码

    boolean setInputMethodEnabledLocked(String id, boolean enabled) {
        // Make sure this is a valid input method.
        InputMethodInfo imm = mMethodMap.get(id);
        if (imm == null) {
            throw new IllegalArgumentException("Unknown id: " + mCurMethodId);
        }

        List<Pair<String, ArrayList<String>>> enabledInputMethodsList = mSettings
                .getEnabledInputMethodsAndSubtypeListLocked();

        if (enabled) {
            for (Pair<String, ArrayList<String>> pair: enabledInputMethodsList) {
                if (pair.first.equals(id)) {
                    // We are enabling this input method, but it is already enabled.
                    // Nothing to do. The previous state was enabled.
                    return true;
                }
            }
            mSettings.appendAndPutEnabledInputMethodLocked(id, false);
            // Previous state was disabled.
            return false;
        } else {
            StringBuilder builder = new StringBuilder();
            if (mSettings.buildAndPutEnabledInputMethodsStrRemovingIdLocked(
                    builder, enabledInputMethodsList, id)) {
                // Disabled input method is currently selected, switch to another one.
                final String selId = mSettings.getSelectedInputMethod();
                if (id.equals(selId) && !chooseNewDefaultIMELocked()) {
                    Slog.i(TAG, "Can't find new IME, unsetting the current input method.");
                    resetSelectedInputMethodAndSubtypeLocked("");
                }
                // Previous state was enabled.
                return true;
            } else {
                // We are disabling the input method but it is already disabled.
                // Nothing to do.  The previous state was disabled.
                return false;
            }
        }
    }

 

5. 流程就走到了InputMethodUtils::putEnabledInputMethodStr,将值写入Settings数据库 putString(Settings.Secure.ENABLED_INPUT_METHODS, str);

//aosp.opersys.com/xref/android-9.0.0_r61/xref/frameworks/base/core/java/com/android/internal/inputmethod/InputMethodUtils.java#1052

//aosp.opersys.com/xref/android-9.0.0_r61/xref/frameworks/base/core/java/com/android/internal/inputmethod/InputMethodUtils.java#1108

 

分析到这里Settings数据库中enabled_input_methods这个key-value就有了默认值了,一般是“com.android.inputmethod.latin/.LatinIME”

 

6. 接着分析,buildInputMethodListLocked()完成后,返回到systemRunning()中继续调用到resetDefaultImeLocked()

    private void resetDefaultImeLocked(Context context) {
        // Do not reset the default (current) IME when it is a 3rd-party IME
        if (mCurMethodId != null && !InputMethodUtils.isSystemIme(mMethodMap.get(mCurMethodId))) {
            return;
        }
        final List<InputMethodInfo> suitableImes = InputMethodUtils.getDefaultEnabledImes(
                context, mSettings.getEnabledInputMethodListLocked());
        if (suitableImes.isEmpty()) {
            Slog.i(TAG, "No default found");
            return;
        }
        final InputMethodInfo defIm = suitableImes.get(0);
        if (DEBUG) {
            Slog.i(TAG, "Default found, using " + defIm.getId());
        }
        setSelectedInputMethodAndSubtypeLocked(defIm, NOT_A_SUBTYPE_ID, false);
    }

 

7. 继续走到setSelectedInputMethodAndSubtypeLocked方法中

点击查看代码

    private void setSelectedInputMethodAndSubtypeLocked(InputMethodInfo imi, int subtypeId,
            boolean setSubtypeOnly) {
        // Updates to InputMethod are transient in VR mode. Its not included in history.
        final boolean isVrInput = imi != null && imi.isVrOnly();
        if (!isVrInput) {
            // Update the history of InputMethod and Subtype
            mSettings.saveCurrentInputMethodAndSubtypeToHistory(mCurMethodId, mCurrentSubtype);
        }

        mCurUserActionNotificationSequenceNumber =
                Math.max(mCurUserActionNotificationSequenceNumber + 1, 1);
        if (DEBUG) {
            Slog.d(TAG, "Bump mCurUserActionNotificationSequenceNumber:"
                    + mCurUserActionNotificationSequenceNumber);
        }

        if (mCurClient != null && mCurClient.client != null) {
            executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIO(
                    MSG_SET_USER_ACTION_NOTIFICATION_SEQUENCE_NUMBER,
                    mCurUserActionNotificationSequenceNumber, mCurClient));
        }

        if (isVrInput) {
            // Updates to InputMethod are transient in VR mode. Any changes to Settings are skipped.
            return;
        }

        // Set Subtype here
        if (imi == null || subtypeId < 0) {
            mSettings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
            mCurrentSubtype = null;
        } else {
            if (subtypeId < imi.getSubtypeCount()) {
                InputMethodSubtype subtype = imi.getSubtypeAt(subtypeId);
                mSettings.putSelectedSubtype(subtype.hashCode());
                mCurrentSubtype = subtype;
            } else {
                mSettings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
                // If the subtype is not specified, choose the most applicable one
                mCurrentSubtype = getCurrentInputMethodSubtypeLocked();
            }
        }

        if (!setSubtypeOnly) {
            // Set InputMethod here
            mSettings.putSelectedInputMethod(imi != null ? imi.getId() : "");
        }
    }

 

8. mSettings.putSelectedInputMethod(imi != null ? imi.getId() : “”) ==> putSelectedInputMethod==>putString(Settings.Secure.DEFAULT_INPUT_METHOD, imeId)
最终将值写入Settings数据库中的default_input_method

//aosp.opersys.com/xref/android-9.0.0_r61/xref/frameworks/base/core/java/com/android/internal/inputmethod/InputMethodUtils.java#1315

 

至此default_input_method这个key-value也有了默认值