Android 9.0 預設輸入法的設定流程分析

Hi,出發了發表於2021-08-24

Android 輸入法設定文章

  Android 9.0 預設輸入法的設定流程分析

  Android 9.0 新增預置第三方輸入法/設定預設輸入法(軟鍵盤)

 

前言

在上一篇文章  Android 9.0 新增預置第三方輸入法/設定預設輸入法(軟鍵盤)    中我們可以通過設定enabled_input_methods和default_input_method兩個key-value的值來顯示的指定可選的輸入法及預設輸入法。

但是,檢視Android原生程式碼,並沒任何地方顯示的設定這兩個值,但是當開機後,我們去console先檢視,這兩個值卻被設定為了google原生輸入法,那這兩個值是在哪裡設定的呢?本篇將簡單介紹

Android 9.0 預設輸入法的設定流程分析

設定流程分析

1.  Android系統開機後,當ActivityManagerService及PackageManagerService都ready後,systemserver會回撥到InputMethodManagerService::systemRunning()方法http://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方法,部分原始碼如下:

http://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)去做設定

http://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);

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

http://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

http://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也有了預設值

 

相關文章