InputMethodUtils.java revision dc489241cfb3691a87942344cf55efd3d98c1107
1/*
2 * Copyright (C) 2013 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.internal.inputmethod;
18
19import android.content.ContentResolver;
20import android.content.Context;
21import android.content.pm.ApplicationInfo;
22import android.content.pm.PackageManager;
23import android.content.pm.PackageManager.NameNotFoundException;
24import android.content.res.Resources;
25import android.provider.Settings;
26import android.provider.Settings.SettingNotFoundException;
27import android.text.TextUtils;
28import android.util.Pair;
29import android.util.Slog;
30import android.view.inputmethod.InputMethodInfo;
31import android.view.inputmethod.InputMethodSubtype;
32import android.view.textservice.SpellCheckerInfo;
33import android.view.textservice.TextServicesManager;
34
35import java.util.ArrayList;
36import java.util.HashMap;
37import java.util.List;
38import java.util.Locale;
39
40/**
41 * InputMethodManagerUtils contains some static methods that provides IME informations.
42 * This methods are supposed to be used in both the framework and the Settings application.
43 */
44public class InputMethodUtils {
45    public static final boolean DEBUG = false;
46    public static final int NOT_A_SUBTYPE_ID = -1;
47    public static final String SUBTYPE_MODE_ANY = null;
48    public static final String SUBTYPE_MODE_KEYBOARD = "keyboard";
49    public static final String SUBTYPE_MODE_VOICE = "voice";
50    private static final String TAG = "InputMethodUtils";
51    private static final Locale ENGLISH_LOCALE = new Locale("en");
52    private static final String NOT_A_SUBTYPE_ID_STR = String.valueOf(NOT_A_SUBTYPE_ID);
53    private static final String TAG_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE =
54            "EnabledWhenDefaultIsNotAsciiCapable";
55    private static final String TAG_ASCII_CAPABLE = "AsciiCapable";
56    /**
57     * Used in {@link #getFallbackLocaleForDefaultIme(ArrayList, Context)} to find the fallback IMEs
58     * that are mainly used until the system becomes ready. Note that {@link Locale} in this array
59     * is checked with {@link Locale#equals(Object)}, which means that {@code Locale.ENGLISH}
60     * doesn't automatically match {@code Locale("en", "IN")}.
61     */
62    private static final Locale[] SEARCH_ORDER_OF_FALLBACK_LOCALES = {
63        Locale.ENGLISH, // "en"
64        Locale.US, // "en_US"
65        Locale.UK, // "en_GB"
66    };
67
68    private InputMethodUtils() {
69        // This utility class is not publicly instantiable.
70    }
71
72    // ----------------------------------------------------------------------
73    // Utilities for debug
74    public static String getStackTrace() {
75        final StringBuilder sb = new StringBuilder();
76        try {
77            throw new RuntimeException();
78        } catch (RuntimeException e) {
79            final StackTraceElement[] frames = e.getStackTrace();
80            // Start at 1 because the first frame is here and we don't care about it
81            for (int j = 1; j < frames.length; ++j) {
82                sb.append(frames[j].toString() + "\n");
83            }
84        }
85        return sb.toString();
86    }
87
88    public static String getApiCallStack() {
89        String apiCallStack = "";
90        try {
91            throw new RuntimeException();
92        } catch (RuntimeException e) {
93            final StackTraceElement[] frames = e.getStackTrace();
94            for (int j = 1; j < frames.length; ++j) {
95                final String tempCallStack = frames[j].toString();
96                if (TextUtils.isEmpty(apiCallStack)) {
97                    // Overwrite apiCallStack if it's empty
98                    apiCallStack = tempCallStack;
99                } else if (tempCallStack.indexOf("Transact(") < 0) {
100                    // Overwrite apiCallStack if it's not a binder call
101                    apiCallStack = tempCallStack;
102                } else {
103                    break;
104                }
105            }
106        }
107        return apiCallStack;
108    }
109    // ----------------------------------------------------------------------
110
111    public static boolean isSystemIme(InputMethodInfo inputMethod) {
112        return (inputMethod.getServiceInfo().applicationInfo.flags
113                & ApplicationInfo.FLAG_SYSTEM) != 0;
114    }
115
116    /**
117     * @deprecated Use {@link Locale} returned from
118     * {@link #getFallbackLocaleForDefaultIme(ArrayList)} instead.
119     */
120    @Deprecated
121    public static boolean isSystemImeThatHasEnglishKeyboardSubtype(InputMethodInfo imi) {
122        if (!isSystemIme(imi)) {
123            return false;
124        }
125        return containsSubtypeOf(imi, ENGLISH_LOCALE.getLanguage(), SUBTYPE_MODE_KEYBOARD);
126    }
127
128    public static Locale getFallbackLocaleForDefaultIme(final ArrayList<InputMethodInfo> imis,
129            final Context context) {
130        for (final Locale fallbackLocale : SEARCH_ORDER_OF_FALLBACK_LOCALES) {
131            for (int i = 0; i < imis.size(); ++i) {
132                final InputMethodInfo imi = imis.get(i);
133                if (isSystemIme(imi) && imi.isDefault(context) &&
134                        containsSubtypeOf(imi, fallbackLocale, false /* ignoreCountry */,
135                                SUBTYPE_MODE_KEYBOARD)) {
136                    return fallbackLocale;
137                }
138            }
139        }
140        return null;
141    }
142
143    private static boolean isSystemAuxilialyImeThatHasAutomaticSubtype(InputMethodInfo imi) {
144        if (!isSystemIme(imi)) {
145            return false;
146        }
147        if (!imi.isAuxiliaryIme()) {
148            return false;
149        }
150        final int subtypeCount = imi.getSubtypeCount();
151        for (int i = 0; i < subtypeCount; ++i) {
152            final InputMethodSubtype s = imi.getSubtypeAt(i);
153            if (s.overridesImplicitlyEnabledSubtype()) {
154                return true;
155            }
156        }
157        return false;
158    }
159
160    public static Locale getSystemLocaleFromContext(final Context context) {
161        try {
162            return context.getResources().getConfiguration().locale;
163        } catch (Resources.NotFoundException ex) {
164            return null;
165        }
166    }
167
168    public static ArrayList<InputMethodInfo> getDefaultEnabledImes(
169            Context context, boolean isSystemReady, ArrayList<InputMethodInfo> imis) {
170        // OK to store null in fallbackLocale because isImeThatHasSubtypeOf() is null-tolerant.
171        final Locale fallbackLocale = getFallbackLocaleForDefaultIme(imis, context);
172
173        if (!isSystemReady) {
174            final ArrayList<InputMethodInfo> retval = new ArrayList<>();
175            for (int i = 0; i < imis.size(); ++i) {
176                final InputMethodInfo imi = imis.get(i);
177                // TODO: We should check isAsciiCapable instead of relying on fallbackLocale.
178                if (isSystemIme(imi) && imi.isDefault(context) &&
179                        isImeThatHasSubtypeOf(imi, fallbackLocale, false /* ignoreCountry */,
180                                SUBTYPE_MODE_KEYBOARD)) {
181                    retval.add(imi);
182                }
183            }
184            return retval;
185        }
186
187        // OK to store null in fallbackLocale because isImeThatHasSubtypeOf() is null-tolerant.
188        final Locale systemLocale = getSystemLocaleFromContext(context);
189        // TODO: Use LinkedHashSet to simplify the code.
190        final ArrayList<InputMethodInfo> retval = new ArrayList<>();
191        boolean systemLocaleKeyboardImeFound = false;
192
193        // First, try to find IMEs with taking the system locale country into consideration.
194        for (int i = 0; i < imis.size(); ++i) {
195            final InputMethodInfo imi = imis.get(i);
196            if (!isSystemIme(imi) || !imi.isDefault(context)) {
197                continue;
198            }
199            final boolean isSystemLocaleKeyboardIme = isImeThatHasSubtypeOf(imi, systemLocale,
200                    false /* ignoreCountry */, SUBTYPE_MODE_KEYBOARD);
201            // TODO: We should check isAsciiCapable instead of relying on fallbackLocale.
202            // TODO: Use LinkedHashSet to simplify the code.
203            if (isSystemLocaleKeyboardIme ||
204                    isImeThatHasSubtypeOf(imi, fallbackLocale, false /* ignoreCountry */,
205                            SUBTYPE_MODE_ANY)) {
206                retval.add(imi);
207            }
208            systemLocaleKeyboardImeFound |= isSystemLocaleKeyboardIme;
209        }
210
211        // System locale country doesn't match any IMEs, try to find IMEs in a country-agnostic
212        // way.
213        if (!systemLocaleKeyboardImeFound) {
214            for (int i = 0; i < imis.size(); ++i) {
215                final InputMethodInfo imi = imis.get(i);
216                if (!isSystemIme(imi) || !imi.isDefault(context)) {
217                    continue;
218                }
219                if (isImeThatHasSubtypeOf(imi, fallbackLocale, false /* ignoreCountry */,
220                        SUBTYPE_MODE_KEYBOARD)) {
221                    // IMEs that have fallback locale are already added in the previous loop. We
222                    // don't need to add them again here.
223                    // TODO: Use LinkedHashSet to simplify the code.
224                    continue;
225                }
226                if (isImeThatHasSubtypeOf(imi, systemLocale, true /* ignoreCountry */,
227                        SUBTYPE_MODE_ANY)) {
228                    retval.add(imi);
229                }
230            }
231        }
232
233        // If one or more auxiliary input methods are available, OK to stop populating the list.
234        for (int i = 0; i < retval.size(); ++i) {
235            if (retval.get(i).isAuxiliaryIme()) {
236                return retval;
237            }
238        }
239        for (int i = 0; i < imis.size(); ++i) {
240            final InputMethodInfo imi = imis.get(i);
241            if (isSystemAuxilialyImeThatHasAutomaticSubtype(imi)) {
242                retval.add(imi);
243            }
244        }
245        return retval;
246    }
247
248    public static boolean isImeThatHasSubtypeOf(final InputMethodInfo imi,
249            final Locale locale, final boolean ignoreCountry, final String mode) {
250        if (locale == null) {
251            return false;
252        }
253        return containsSubtypeOf(imi, locale, ignoreCountry, mode);
254    }
255
256    /**
257     * @deprecated Use {@link #isSystemIme(InputMethodInfo)} and
258     * {@link InputMethodInfo#isDefault(Context)} and
259     * {@link #isImeThatHasSubtypeOf(InputMethodInfo, Locale, boolean, String))} instead.
260     */
261    @Deprecated
262    public static boolean isValidSystemDefaultIme(
263            boolean isSystemReady, InputMethodInfo imi, Context context) {
264        if (!isSystemReady) {
265            return false;
266        }
267        if (!isSystemIme(imi)) {
268            return false;
269        }
270        if (imi.getIsDefaultResourceId() != 0) {
271            try {
272                if (imi.isDefault(context) && containsSubtypeOf(
273                        imi, context.getResources().getConfiguration().locale.getLanguage(),
274                        SUBTYPE_MODE_ANY)) {
275                    return true;
276                }
277            } catch (Resources.NotFoundException ex) {
278            }
279        }
280        if (imi.getSubtypeCount() == 0) {
281            Slog.w(TAG, "Found no subtypes in a system IME: " + imi.getPackageName());
282        }
283        return false;
284    }
285
286    public static boolean containsSubtypeOf(final InputMethodInfo imi,
287            final Locale locale, final boolean ignoreCountry, final String mode) {
288        final int N = imi.getSubtypeCount();
289        for (int i = 0; i < N; ++i) {
290            final InputMethodSubtype subtype = imi.getSubtypeAt(i);
291            if (ignoreCountry) {
292                final Locale subtypeLocale = new Locale(getLanguageFromLocaleString(
293                        subtype.getLocale()));
294                if (!subtypeLocale.getLanguage().equals(locale.getLanguage())) {
295                    continue;
296                }
297            } else {
298                // TODO: Use {@link Locale#toLanguageTag()} and
299                // {@link Locale#forLanguageTag(languageTag)} instead.
300                if (!TextUtils.equals(subtype.getLocale(), locale.toString())) {
301                    continue;
302                }
303            }
304            if (mode == SUBTYPE_MODE_ANY || TextUtils.isEmpty(mode) ||
305                    mode.equalsIgnoreCase(subtype.getMode())) {
306                return true;
307            }
308        }
309        return false;
310    }
311
312    /**
313     * @deprecated Use {@link #containsSubtypeOf(InputMethodInfo, Locale, boolean, String)} instead.
314     */
315    @Deprecated
316    public static boolean containsSubtypeOf(InputMethodInfo imi, String language, String mode) {
317        final int N = imi.getSubtypeCount();
318        for (int i = 0; i < N; ++i) {
319            final InputMethodSubtype subtype = imi.getSubtypeAt(i);
320            if (!subtype.getLocale().startsWith(language)) {
321                continue;
322            }
323            if (mode == SUBTYPE_MODE_ANY || TextUtils.isEmpty(mode) ||
324                    mode.equalsIgnoreCase(subtype.getMode())) {
325                return true;
326            }
327        }
328        return false;
329    }
330
331    public static ArrayList<InputMethodSubtype> getSubtypes(InputMethodInfo imi) {
332        ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>();
333        final int subtypeCount = imi.getSubtypeCount();
334        for (int i = 0; i < subtypeCount; ++i) {
335            subtypes.add(imi.getSubtypeAt(i));
336        }
337        return subtypes;
338    }
339
340    public static ArrayList<InputMethodSubtype> getOverridingImplicitlyEnabledSubtypes(
341            InputMethodInfo imi, String mode) {
342        ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>();
343        final int subtypeCount = imi.getSubtypeCount();
344        for (int i = 0; i < subtypeCount; ++i) {
345            final InputMethodSubtype subtype = imi.getSubtypeAt(i);
346            if (subtype.overridesImplicitlyEnabledSubtype() && subtype.getMode().equals(mode)) {
347                subtypes.add(subtype);
348            }
349        }
350        return subtypes;
351    }
352
353    public static InputMethodInfo getMostApplicableDefaultIME(List<InputMethodInfo> enabledImes) {
354        if (enabledImes == null || enabledImes.isEmpty()) {
355            return null;
356        }
357        // We'd prefer to fall back on a system IME, since that is safer.
358        int i = enabledImes.size();
359        int firstFoundSystemIme = -1;
360        while (i > 0) {
361            i--;
362            final InputMethodInfo imi = enabledImes.get(i);
363            if (InputMethodUtils.isSystemImeThatHasEnglishKeyboardSubtype(imi)
364                    && !imi.isAuxiliaryIme()) {
365                return imi;
366            }
367            if (firstFoundSystemIme < 0 && InputMethodUtils.isSystemIme(imi)
368                    && !imi.isAuxiliaryIme()) {
369                firstFoundSystemIme = i;
370            }
371        }
372        return enabledImes.get(Math.max(firstFoundSystemIme, 0));
373    }
374
375    public static boolean isValidSubtypeId(InputMethodInfo imi, int subtypeHashCode) {
376        return getSubtypeIdFromHashCode(imi, subtypeHashCode) != NOT_A_SUBTYPE_ID;
377    }
378
379    public static int getSubtypeIdFromHashCode(InputMethodInfo imi, int subtypeHashCode) {
380        if (imi != null) {
381            final int subtypeCount = imi.getSubtypeCount();
382            for (int i = 0; i < subtypeCount; ++i) {
383                InputMethodSubtype ims = imi.getSubtypeAt(i);
384                if (subtypeHashCode == ims.hashCode()) {
385                    return i;
386                }
387            }
388        }
389        return NOT_A_SUBTYPE_ID;
390    }
391
392    private static ArrayList<InputMethodSubtype> getImplicitlyApplicableSubtypesLocked(
393            Resources res, InputMethodInfo imi) {
394        final List<InputMethodSubtype> subtypes = InputMethodUtils.getSubtypes(imi);
395        final String systemLocale = res.getConfiguration().locale.toString();
396        if (TextUtils.isEmpty(systemLocale)) return new ArrayList<InputMethodSubtype>();
397        final String systemLanguage = res.getConfiguration().locale.getLanguage();
398        final HashMap<String, InputMethodSubtype> applicableModeAndSubtypesMap =
399                new HashMap<String, InputMethodSubtype>();
400        final int N = subtypes.size();
401        for (int i = 0; i < N; ++i) {
402            // scan overriding implicitly enabled subtypes.
403            InputMethodSubtype subtype = subtypes.get(i);
404            if (subtype.overridesImplicitlyEnabledSubtype()) {
405                final String mode = subtype.getMode();
406                if (!applicableModeAndSubtypesMap.containsKey(mode)) {
407                    applicableModeAndSubtypesMap.put(mode, subtype);
408                }
409            }
410        }
411        if (applicableModeAndSubtypesMap.size() > 0) {
412            return new ArrayList<InputMethodSubtype>(applicableModeAndSubtypesMap.values());
413        }
414        for (int i = 0; i < N; ++i) {
415            final InputMethodSubtype subtype = subtypes.get(i);
416            final String locale = subtype.getLocale();
417            final String mode = subtype.getMode();
418            final String language = getLanguageFromLocaleString(locale);
419            // When system locale starts with subtype's locale, that subtype will be applicable
420            // for system locale. We need to make sure the languages are the same, to prevent
421            // locales like "fil" (Filipino) being matched by "fi" (Finnish).
422            //
423            // For instance, it's clearly applicable for cases like system locale = en_US and
424            // subtype = en, but it is not necessarily considered applicable for cases like system
425            // locale = en and subtype = en_US.
426            //
427            // We just call systemLocale.startsWith(locale) in this function because there is no
428            // need to find applicable subtypes aggressively unlike
429            // findLastResortApplicableSubtypeLocked.
430            //
431            // TODO: This check is broken. It won't take scripts into account and doesn't
432            // account for the mandatory conversions performed by Locale#toString.
433            if (language.equals(systemLanguage) && systemLocale.startsWith(locale)) {
434                final InputMethodSubtype applicableSubtype = applicableModeAndSubtypesMap.get(mode);
435                // If more applicable subtypes are contained, skip.
436                if (applicableSubtype != null) {
437                    if (systemLocale.equals(applicableSubtype.getLocale())) continue;
438                    if (!systemLocale.equals(locale)) continue;
439                }
440                applicableModeAndSubtypesMap.put(mode, subtype);
441            }
442        }
443        final InputMethodSubtype keyboardSubtype
444                = applicableModeAndSubtypesMap.get(SUBTYPE_MODE_KEYBOARD);
445        final ArrayList<InputMethodSubtype> applicableSubtypes = new ArrayList<InputMethodSubtype>(
446                applicableModeAndSubtypesMap.values());
447        if (keyboardSubtype != null && !keyboardSubtype.containsExtraValueKey(TAG_ASCII_CAPABLE)) {
448            for (int i = 0; i < N; ++i) {
449                final InputMethodSubtype subtype = subtypes.get(i);
450                final String mode = subtype.getMode();
451                if (SUBTYPE_MODE_KEYBOARD.equals(mode) && subtype.containsExtraValueKey(
452                        TAG_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE)) {
453                    applicableSubtypes.add(subtype);
454                }
455            }
456        }
457        if (keyboardSubtype == null) {
458            InputMethodSubtype lastResortKeyboardSubtype = findLastResortApplicableSubtypeLocked(
459                    res, subtypes, SUBTYPE_MODE_KEYBOARD, systemLocale, true);
460            if (lastResortKeyboardSubtype != null) {
461                applicableSubtypes.add(lastResortKeyboardSubtype);
462            }
463        }
464        return applicableSubtypes;
465    }
466
467    private static List<InputMethodSubtype> getEnabledInputMethodSubtypeList(
468            Context context, InputMethodInfo imi, List<InputMethodSubtype> enabledSubtypes,
469            boolean allowsImplicitlySelectedSubtypes) {
470        if (allowsImplicitlySelectedSubtypes && enabledSubtypes.isEmpty()) {
471            enabledSubtypes = InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
472                    context.getResources(), imi);
473        }
474        return InputMethodSubtype.sort(context, 0, imi, enabledSubtypes);
475    }
476
477    /**
478     * Returns the language component of a given locale string.
479     * TODO: Use {@link Locale#toLanguageTag()} and {@link Locale#forLanguageTag(languageTag)}
480     */
481    public static String getLanguageFromLocaleString(String locale) {
482        final int idx = locale.indexOf('_');
483        if (idx < 0) {
484            return locale;
485        } else {
486            return locale.substring(0, idx);
487        }
488    }
489
490    /**
491     * If there are no selected subtypes, tries finding the most applicable one according to the
492     * given locale.
493     * @param subtypes this function will search the most applicable subtype in subtypes
494     * @param mode subtypes will be filtered by mode
495     * @param locale subtypes will be filtered by locale
496     * @param canIgnoreLocaleAsLastResort if this function can't find the most applicable subtype,
497     * it will return the first subtype matched with mode
498     * @return the most applicable subtypeId
499     */
500    public static InputMethodSubtype findLastResortApplicableSubtypeLocked(
501            Resources res, List<InputMethodSubtype> subtypes, String mode, String locale,
502            boolean canIgnoreLocaleAsLastResort) {
503        if (subtypes == null || subtypes.size() == 0) {
504            return null;
505        }
506        if (TextUtils.isEmpty(locale)) {
507            locale = res.getConfiguration().locale.toString();
508        }
509        final String language = getLanguageFromLocaleString(locale);
510        boolean partialMatchFound = false;
511        InputMethodSubtype applicableSubtype = null;
512        InputMethodSubtype firstMatchedModeSubtype = null;
513        final int N = subtypes.size();
514        for (int i = 0; i < N; ++i) {
515            InputMethodSubtype subtype = subtypes.get(i);
516            final String subtypeLocale = subtype.getLocale();
517            final String subtypeLanguage = getLanguageFromLocaleString(subtypeLocale);
518            // An applicable subtype should match "mode". If mode is null, mode will be ignored,
519            // and all subtypes with all modes can be candidates.
520            if (mode == null || subtypes.get(i).getMode().equalsIgnoreCase(mode)) {
521                if (firstMatchedModeSubtype == null) {
522                    firstMatchedModeSubtype = subtype;
523                }
524                if (locale.equals(subtypeLocale)) {
525                    // Exact match (e.g. system locale is "en_US" and subtype locale is "en_US")
526                    applicableSubtype = subtype;
527                    break;
528                } else if (!partialMatchFound && language.equals(subtypeLanguage)) {
529                    // Partial match (e.g. system locale is "en_US" and subtype locale is "en")
530                    applicableSubtype = subtype;
531                    partialMatchFound = true;
532                }
533            }
534        }
535
536        if (applicableSubtype == null && canIgnoreLocaleAsLastResort) {
537            return firstMatchedModeSubtype;
538        }
539
540        // The first subtype applicable to the system locale will be defined as the most applicable
541        // subtype.
542        if (DEBUG) {
543            if (applicableSubtype != null) {
544                Slog.d(TAG, "Applicable InputMethodSubtype was found: "
545                        + applicableSubtype.getMode() + "," + applicableSubtype.getLocale());
546            }
547        }
548        return applicableSubtype;
549    }
550
551    public static boolean canAddToLastInputMethod(InputMethodSubtype subtype) {
552        if (subtype == null) return true;
553        return !subtype.isAuxiliary();
554    }
555
556    public static void setNonSelectedSystemImesDisabledUntilUsed(
557            PackageManager packageManager, List<InputMethodInfo> enabledImis) {
558        if (DEBUG) {
559            Slog.d(TAG, "setNonSelectedSystemImesDisabledUntilUsed");
560        }
561        final String[] systemImesDisabledUntilUsed = Resources.getSystem().getStringArray(
562                com.android.internal.R.array.config_disabledUntilUsedPreinstalledImes);
563        if (systemImesDisabledUntilUsed == null || systemImesDisabledUntilUsed.length == 0) {
564            return;
565        }
566        // Only the current spell checker should be treated as an enabled one.
567        final SpellCheckerInfo currentSpellChecker =
568                TextServicesManager.getInstance().getCurrentSpellChecker();
569        for (final String packageName : systemImesDisabledUntilUsed) {
570            if (DEBUG) {
571                Slog.d(TAG, "check " + packageName);
572            }
573            boolean enabledIme = false;
574            for (int j = 0; j < enabledImis.size(); ++j) {
575                final InputMethodInfo imi = enabledImis.get(j);
576                if (packageName.equals(imi.getPackageName())) {
577                    enabledIme = true;
578                    break;
579                }
580            }
581            if (enabledIme) {
582                // enabled ime. skip
583                continue;
584            }
585            if (currentSpellChecker != null
586                    && packageName.equals(currentSpellChecker.getPackageName())) {
587                // enabled spell checker. skip
588                if (DEBUG) {
589                    Slog.d(TAG, packageName + " is the current spell checker. skip");
590                }
591                continue;
592            }
593            ApplicationInfo ai = null;
594            try {
595                ai = packageManager.getApplicationInfo(packageName,
596                        PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS);
597            } catch (NameNotFoundException e) {
598                Slog.w(TAG, "NameNotFoundException: " + packageName, e);
599            }
600            if (ai == null) {
601                // No app found for packageName
602                continue;
603            }
604            final boolean isSystemPackage = (ai.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
605            if (!isSystemPackage) {
606                continue;
607            }
608            setDisabledUntilUsed(packageManager, packageName);
609        }
610    }
611
612    private static void setDisabledUntilUsed(PackageManager packageManager, String packageName) {
613        final int state = packageManager.getApplicationEnabledSetting(packageName);
614        if (state == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
615                || state == PackageManager.COMPONENT_ENABLED_STATE_ENABLED) {
616            if (DEBUG) {
617                Slog.d(TAG, "Update state(" + packageName + "): DISABLED_UNTIL_USED");
618            }
619            packageManager.setApplicationEnabledSetting(packageName,
620                    PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED, 0);
621        } else {
622            if (DEBUG) {
623                Slog.d(TAG, packageName + " is already DISABLED_UNTIL_USED");
624            }
625        }
626    }
627
628    public static CharSequence getImeAndSubtypeDisplayName(Context context, InputMethodInfo imi,
629            InputMethodSubtype subtype) {
630        final CharSequence imiLabel = imi.loadLabel(context.getPackageManager());
631        return subtype != null
632                ? TextUtils.concat(subtype.getDisplayName(context,
633                        imi.getPackageName(), imi.getServiceInfo().applicationInfo),
634                                (TextUtils.isEmpty(imiLabel) ?
635                                        "" : " - " + imiLabel))
636                : imiLabel;
637    }
638
639    /**
640     * Utility class for putting and getting settings for InputMethod
641     * TODO: Move all putters and getters of settings to this class.
642     */
643    public static class InputMethodSettings {
644        // The string for enabled input method is saved as follows:
645        // example: ("ime0;subtype0;subtype1;subtype2:ime1:ime2;subtype0")
646        private static final char INPUT_METHOD_SEPARATER = ':';
647        private static final char INPUT_METHOD_SUBTYPE_SEPARATER = ';';
648        private final TextUtils.SimpleStringSplitter mInputMethodSplitter =
649                new TextUtils.SimpleStringSplitter(INPUT_METHOD_SEPARATER);
650
651        private final TextUtils.SimpleStringSplitter mSubtypeSplitter =
652                new TextUtils.SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATER);
653
654        private final Resources mRes;
655        private final ContentResolver mResolver;
656        private final HashMap<String, InputMethodInfo> mMethodMap;
657        private final ArrayList<InputMethodInfo> mMethodList;
658
659        private String mEnabledInputMethodsStrCache;
660        private int mCurrentUserId;
661        private int[] mCurrentProfileIds = new int[0];
662
663        private static void buildEnabledInputMethodsSettingString(
664                StringBuilder builder, Pair<String, ArrayList<String>> pair) {
665            String id = pair.first;
666            ArrayList<String> subtypes = pair.second;
667            builder.append(id);
668            // Inputmethod and subtypes are saved in the settings as follows:
669            // ime0;subtype0;subtype1:ime1;subtype0:ime2:ime3;subtype0;subtype1
670            for (String subtypeId: subtypes) {
671                builder.append(INPUT_METHOD_SUBTYPE_SEPARATER).append(subtypeId);
672            }
673        }
674
675        public InputMethodSettings(
676                Resources res, ContentResolver resolver,
677                HashMap<String, InputMethodInfo> methodMap, ArrayList<InputMethodInfo> methodList,
678                int userId) {
679            setCurrentUserId(userId);
680            mRes = res;
681            mResolver = resolver;
682            mMethodMap = methodMap;
683            mMethodList = methodList;
684        }
685
686        public void setCurrentUserId(int userId) {
687            if (DEBUG) {
688                Slog.d(TAG, "--- Swtich the current user from " + mCurrentUserId + " to " + userId);
689            }
690            // IMMS settings are kept per user, so keep track of current user
691            mCurrentUserId = userId;
692        }
693
694        public void setCurrentProfileIds(int[] currentProfileIds) {
695            synchronized (this) {
696                mCurrentProfileIds = currentProfileIds;
697            }
698        }
699
700        public boolean isCurrentProfile(int userId) {
701            synchronized (this) {
702                if (userId == mCurrentUserId) return true;
703                for (int i = 0; i < mCurrentProfileIds.length; i++) {
704                    if (userId == mCurrentProfileIds[i]) return true;
705                }
706                return false;
707            }
708        }
709
710        public List<InputMethodInfo> getEnabledInputMethodListLocked() {
711            return createEnabledInputMethodListLocked(
712                    getEnabledInputMethodsAndSubtypeListLocked());
713        }
714
715        public List<Pair<InputMethodInfo, ArrayList<String>>>
716                getEnabledInputMethodAndSubtypeHashCodeListLocked() {
717            return createEnabledInputMethodAndSubtypeHashCodeListLocked(
718                    getEnabledInputMethodsAndSubtypeListLocked());
719        }
720
721        public List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked(
722                Context context, InputMethodInfo imi, boolean allowsImplicitlySelectedSubtypes) {
723            List<InputMethodSubtype> enabledSubtypes =
724                    getEnabledInputMethodSubtypeListLocked(imi);
725            if (allowsImplicitlySelectedSubtypes && enabledSubtypes.isEmpty()) {
726                enabledSubtypes = InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
727                        context.getResources(), imi);
728            }
729            return InputMethodSubtype.sort(context, 0, imi, enabledSubtypes);
730        }
731
732        public List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked(
733                InputMethodInfo imi) {
734            List<Pair<String, ArrayList<String>>> imsList =
735                    getEnabledInputMethodsAndSubtypeListLocked();
736            ArrayList<InputMethodSubtype> enabledSubtypes =
737                    new ArrayList<InputMethodSubtype>();
738            if (imi != null) {
739                for (Pair<String, ArrayList<String>> imsPair : imsList) {
740                    InputMethodInfo info = mMethodMap.get(imsPair.first);
741                    if (info != null && info.getId().equals(imi.getId())) {
742                        final int subtypeCount = info.getSubtypeCount();
743                        for (int i = 0; i < subtypeCount; ++i) {
744                            InputMethodSubtype ims = info.getSubtypeAt(i);
745                            for (String s: imsPair.second) {
746                                if (String.valueOf(ims.hashCode()).equals(s)) {
747                                    enabledSubtypes.add(ims);
748                                }
749                            }
750                        }
751                        break;
752                    }
753                }
754            }
755            return enabledSubtypes;
756        }
757
758        // At the initial boot, the settings for input methods are not set,
759        // so we need to enable IME in that case.
760        public void enableAllIMEsIfThereIsNoEnabledIME() {
761            if (TextUtils.isEmpty(getEnabledInputMethodsStr())) {
762                StringBuilder sb = new StringBuilder();
763                final int N = mMethodList.size();
764                for (int i = 0; i < N; i++) {
765                    InputMethodInfo imi = mMethodList.get(i);
766                    Slog.i(TAG, "Adding: " + imi.getId());
767                    if (i > 0) sb.append(':');
768                    sb.append(imi.getId());
769                }
770                putEnabledInputMethodsStr(sb.toString());
771            }
772        }
773
774        public List<Pair<String, ArrayList<String>>> getEnabledInputMethodsAndSubtypeListLocked() {
775            ArrayList<Pair<String, ArrayList<String>>> imsList
776                    = new ArrayList<Pair<String, ArrayList<String>>>();
777            final String enabledInputMethodsStr = getEnabledInputMethodsStr();
778            if (TextUtils.isEmpty(enabledInputMethodsStr)) {
779                return imsList;
780            }
781            mInputMethodSplitter.setString(enabledInputMethodsStr);
782            while (mInputMethodSplitter.hasNext()) {
783                String nextImsStr = mInputMethodSplitter.next();
784                mSubtypeSplitter.setString(nextImsStr);
785                if (mSubtypeSplitter.hasNext()) {
786                    ArrayList<String> subtypeHashes = new ArrayList<String>();
787                    // The first element is ime id.
788                    String imeId = mSubtypeSplitter.next();
789                    while (mSubtypeSplitter.hasNext()) {
790                        subtypeHashes.add(mSubtypeSplitter.next());
791                    }
792                    imsList.add(new Pair<String, ArrayList<String>>(imeId, subtypeHashes));
793                }
794            }
795            return imsList;
796        }
797
798        public void appendAndPutEnabledInputMethodLocked(String id, boolean reloadInputMethodStr) {
799            if (reloadInputMethodStr) {
800                getEnabledInputMethodsStr();
801            }
802            if (TextUtils.isEmpty(mEnabledInputMethodsStrCache)) {
803                // Add in the newly enabled input method.
804                putEnabledInputMethodsStr(id);
805            } else {
806                putEnabledInputMethodsStr(
807                        mEnabledInputMethodsStrCache + INPUT_METHOD_SEPARATER + id);
808            }
809        }
810
811        /**
812         * Build and put a string of EnabledInputMethods with removing specified Id.
813         * @return the specified id was removed or not.
814         */
815        public boolean buildAndPutEnabledInputMethodsStrRemovingIdLocked(
816                StringBuilder builder, List<Pair<String, ArrayList<String>>> imsList, String id) {
817            boolean isRemoved = false;
818            boolean needsAppendSeparator = false;
819            for (Pair<String, ArrayList<String>> ims: imsList) {
820                String curId = ims.first;
821                if (curId.equals(id)) {
822                    // We are disabling this input method, and it is
823                    // currently enabled.  Skip it to remove from the
824                    // new list.
825                    isRemoved = true;
826                } else {
827                    if (needsAppendSeparator) {
828                        builder.append(INPUT_METHOD_SEPARATER);
829                    } else {
830                        needsAppendSeparator = true;
831                    }
832                    buildEnabledInputMethodsSettingString(builder, ims);
833                }
834            }
835            if (isRemoved) {
836                // Update the setting with the new list of input methods.
837                putEnabledInputMethodsStr(builder.toString());
838            }
839            return isRemoved;
840        }
841
842        private List<InputMethodInfo> createEnabledInputMethodListLocked(
843                List<Pair<String, ArrayList<String>>> imsList) {
844            final ArrayList<InputMethodInfo> res = new ArrayList<InputMethodInfo>();
845            for (Pair<String, ArrayList<String>> ims: imsList) {
846                InputMethodInfo info = mMethodMap.get(ims.first);
847                if (info != null) {
848                    res.add(info);
849                }
850            }
851            return res;
852        }
853
854        private List<Pair<InputMethodInfo, ArrayList<String>>>
855                createEnabledInputMethodAndSubtypeHashCodeListLocked(
856                        List<Pair<String, ArrayList<String>>> imsList) {
857            final ArrayList<Pair<InputMethodInfo, ArrayList<String>>> res
858                    = new ArrayList<Pair<InputMethodInfo, ArrayList<String>>>();
859            for (Pair<String, ArrayList<String>> ims : imsList) {
860                InputMethodInfo info = mMethodMap.get(ims.first);
861                if (info != null) {
862                    res.add(new Pair<InputMethodInfo, ArrayList<String>>(info, ims.second));
863                }
864            }
865            return res;
866        }
867
868        private void putEnabledInputMethodsStr(String str) {
869            Settings.Secure.putStringForUser(
870                    mResolver, Settings.Secure.ENABLED_INPUT_METHODS, str, mCurrentUserId);
871            mEnabledInputMethodsStrCache = str;
872            if (DEBUG) {
873                Slog.d(TAG, "putEnabledInputMethodStr: " + str);
874            }
875        }
876
877        public String getEnabledInputMethodsStr() {
878            mEnabledInputMethodsStrCache = Settings.Secure.getStringForUser(
879                    mResolver, Settings.Secure.ENABLED_INPUT_METHODS, mCurrentUserId);
880            if (DEBUG) {
881                Slog.d(TAG, "getEnabledInputMethodsStr: " + mEnabledInputMethodsStrCache
882                        + ", " + mCurrentUserId);
883            }
884            return mEnabledInputMethodsStrCache;
885        }
886
887        private void saveSubtypeHistory(
888                List<Pair<String, String>> savedImes, String newImeId, String newSubtypeId) {
889            StringBuilder builder = new StringBuilder();
890            boolean isImeAdded = false;
891            if (!TextUtils.isEmpty(newImeId) && !TextUtils.isEmpty(newSubtypeId)) {
892                builder.append(newImeId).append(INPUT_METHOD_SUBTYPE_SEPARATER).append(
893                        newSubtypeId);
894                isImeAdded = true;
895            }
896            for (Pair<String, String> ime: savedImes) {
897                String imeId = ime.first;
898                String subtypeId = ime.second;
899                if (TextUtils.isEmpty(subtypeId)) {
900                    subtypeId = NOT_A_SUBTYPE_ID_STR;
901                }
902                if (isImeAdded) {
903                    builder.append(INPUT_METHOD_SEPARATER);
904                } else {
905                    isImeAdded = true;
906                }
907                builder.append(imeId).append(INPUT_METHOD_SUBTYPE_SEPARATER).append(
908                        subtypeId);
909            }
910            // Remove the last INPUT_METHOD_SEPARATER
911            putSubtypeHistoryStr(builder.toString());
912        }
913
914        private void addSubtypeToHistory(String imeId, String subtypeId) {
915            List<Pair<String, String>> subtypeHistory = loadInputMethodAndSubtypeHistoryLocked();
916            for (Pair<String, String> ime: subtypeHistory) {
917                if (ime.first.equals(imeId)) {
918                    if (DEBUG) {
919                        Slog.v(TAG, "Subtype found in the history: " + imeId + ", "
920                                + ime.second);
921                    }
922                    // We should break here
923                    subtypeHistory.remove(ime);
924                    break;
925                }
926            }
927            if (DEBUG) {
928                Slog.v(TAG, "Add subtype to the history: " + imeId + ", " + subtypeId);
929            }
930            saveSubtypeHistory(subtypeHistory, imeId, subtypeId);
931        }
932
933        private void putSubtypeHistoryStr(String str) {
934            if (DEBUG) {
935                Slog.d(TAG, "putSubtypeHistoryStr: " + str);
936            }
937            Settings.Secure.putStringForUser(
938                    mResolver, Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY, str, mCurrentUserId);
939        }
940
941        public Pair<String, String> getLastInputMethodAndSubtypeLocked() {
942            // Gets the first one from the history
943            return getLastSubtypeForInputMethodLockedInternal(null);
944        }
945
946        public String getLastSubtypeForInputMethodLocked(String imeId) {
947            Pair<String, String> ime = getLastSubtypeForInputMethodLockedInternal(imeId);
948            if (ime != null) {
949                return ime.second;
950            } else {
951                return null;
952            }
953        }
954
955        private Pair<String, String> getLastSubtypeForInputMethodLockedInternal(String imeId) {
956            List<Pair<String, ArrayList<String>>> enabledImes =
957                    getEnabledInputMethodsAndSubtypeListLocked();
958            List<Pair<String, String>> subtypeHistory = loadInputMethodAndSubtypeHistoryLocked();
959            for (Pair<String, String> imeAndSubtype : subtypeHistory) {
960                final String imeInTheHistory = imeAndSubtype.first;
961                // If imeId is empty, returns the first IME and subtype in the history
962                if (TextUtils.isEmpty(imeId) || imeInTheHistory.equals(imeId)) {
963                    final String subtypeInTheHistory = imeAndSubtype.second;
964                    final String subtypeHashCode =
965                            getEnabledSubtypeHashCodeForInputMethodAndSubtypeLocked(
966                                    enabledImes, imeInTheHistory, subtypeInTheHistory);
967                    if (!TextUtils.isEmpty(subtypeHashCode)) {
968                        if (DEBUG) {
969                            Slog.d(TAG, "Enabled subtype found in the history: " + subtypeHashCode);
970                        }
971                        return new Pair<String, String>(imeInTheHistory, subtypeHashCode);
972                    }
973                }
974            }
975            if (DEBUG) {
976                Slog.d(TAG, "No enabled IME found in the history");
977            }
978            return null;
979        }
980
981        private String getEnabledSubtypeHashCodeForInputMethodAndSubtypeLocked(List<Pair<String,
982                ArrayList<String>>> enabledImes, String imeId, String subtypeHashCode) {
983            for (Pair<String, ArrayList<String>> enabledIme: enabledImes) {
984                if (enabledIme.first.equals(imeId)) {
985                    final ArrayList<String> explicitlyEnabledSubtypes = enabledIme.second;
986                    final InputMethodInfo imi = mMethodMap.get(imeId);
987                    if (explicitlyEnabledSubtypes.size() == 0) {
988                        // If there are no explicitly enabled subtypes, applicable subtypes are
989                        // enabled implicitly.
990                        // If IME is enabled and no subtypes are enabled, applicable subtypes
991                        // are enabled implicitly, so needs to treat them to be enabled.
992                        if (imi != null && imi.getSubtypeCount() > 0) {
993                            List<InputMethodSubtype> implicitlySelectedSubtypes =
994                                    getImplicitlyApplicableSubtypesLocked(mRes, imi);
995                            if (implicitlySelectedSubtypes != null) {
996                                final int N = implicitlySelectedSubtypes.size();
997                                for (int i = 0; i < N; ++i) {
998                                    final InputMethodSubtype st = implicitlySelectedSubtypes.get(i);
999                                    if (String.valueOf(st.hashCode()).equals(subtypeHashCode)) {
1000                                        return subtypeHashCode;
1001                                    }
1002                                }
1003                            }
1004                        }
1005                    } else {
1006                        for (String s: explicitlyEnabledSubtypes) {
1007                            if (s.equals(subtypeHashCode)) {
1008                                // If both imeId and subtypeId are enabled, return subtypeId.
1009                                try {
1010                                    final int hashCode = Integer.valueOf(subtypeHashCode);
1011                                    // Check whether the subtype id is valid or not
1012                                    if (isValidSubtypeId(imi, hashCode)) {
1013                                        return s;
1014                                    } else {
1015                                        return NOT_A_SUBTYPE_ID_STR;
1016                                    }
1017                                } catch (NumberFormatException e) {
1018                                    return NOT_A_SUBTYPE_ID_STR;
1019                                }
1020                            }
1021                        }
1022                    }
1023                    // If imeId was enabled but subtypeId was disabled.
1024                    return NOT_A_SUBTYPE_ID_STR;
1025                }
1026            }
1027            // If both imeId and subtypeId are disabled, return null
1028            return null;
1029        }
1030
1031        private List<Pair<String, String>> loadInputMethodAndSubtypeHistoryLocked() {
1032            ArrayList<Pair<String, String>> imsList = new ArrayList<Pair<String, String>>();
1033            final String subtypeHistoryStr = getSubtypeHistoryStr();
1034            if (TextUtils.isEmpty(subtypeHistoryStr)) {
1035                return imsList;
1036            }
1037            mInputMethodSplitter.setString(subtypeHistoryStr);
1038            while (mInputMethodSplitter.hasNext()) {
1039                String nextImsStr = mInputMethodSplitter.next();
1040                mSubtypeSplitter.setString(nextImsStr);
1041                if (mSubtypeSplitter.hasNext()) {
1042                    String subtypeId = NOT_A_SUBTYPE_ID_STR;
1043                    // The first element is ime id.
1044                    String imeId = mSubtypeSplitter.next();
1045                    while (mSubtypeSplitter.hasNext()) {
1046                        subtypeId = mSubtypeSplitter.next();
1047                        break;
1048                    }
1049                    imsList.add(new Pair<String, String>(imeId, subtypeId));
1050                }
1051            }
1052            return imsList;
1053        }
1054
1055        private String getSubtypeHistoryStr() {
1056            if (DEBUG) {
1057                Slog.d(TAG, "getSubtypeHistoryStr: " + Settings.Secure.getStringForUser(
1058                        mResolver, Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY, mCurrentUserId));
1059            }
1060            return Settings.Secure.getStringForUser(
1061                    mResolver, Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY, mCurrentUserId);
1062        }
1063
1064        public void putSelectedInputMethod(String imeId) {
1065            if (DEBUG) {
1066                Slog.d(TAG, "putSelectedInputMethodStr: " + imeId + ", "
1067                        + mCurrentUserId);
1068            }
1069            Settings.Secure.putStringForUser(
1070                    mResolver, Settings.Secure.DEFAULT_INPUT_METHOD, imeId, mCurrentUserId);
1071        }
1072
1073        public void putSelectedSubtype(int subtypeId) {
1074            if (DEBUG) {
1075                Slog.d(TAG, "putSelectedInputMethodSubtypeStr: " + subtypeId + ", "
1076                        + mCurrentUserId);
1077            }
1078            Settings.Secure.putIntForUser(mResolver, Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE,
1079                    subtypeId, mCurrentUserId);
1080        }
1081
1082        public String getDisabledSystemInputMethods() {
1083            return Settings.Secure.getStringForUser(
1084                    mResolver, Settings.Secure.DISABLED_SYSTEM_INPUT_METHODS, mCurrentUserId);
1085        }
1086
1087        public String getSelectedInputMethod() {
1088            if (DEBUG) {
1089                Slog.d(TAG, "getSelectedInputMethodStr: " + Settings.Secure.getStringForUser(
1090                        mResolver, Settings.Secure.DEFAULT_INPUT_METHOD, mCurrentUserId)
1091                        + ", " + mCurrentUserId);
1092            }
1093            return Settings.Secure.getStringForUser(
1094                    mResolver, Settings.Secure.DEFAULT_INPUT_METHOD, mCurrentUserId);
1095        }
1096
1097        public boolean isSubtypeSelected() {
1098            return getSelectedInputMethodSubtypeHashCode() != NOT_A_SUBTYPE_ID;
1099        }
1100
1101        private int getSelectedInputMethodSubtypeHashCode() {
1102            try {
1103                return Settings.Secure.getIntForUser(
1104                        mResolver, Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE, mCurrentUserId);
1105            } catch (SettingNotFoundException e) {
1106                return NOT_A_SUBTYPE_ID;
1107            }
1108        }
1109
1110        public boolean isShowImeWithHardKeyboardEnabled() {
1111                return Settings.Secure.getIntForUser(mResolver,
1112                        Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD, 0, mCurrentUserId) == 1;
1113        }
1114
1115        public void setShowImeWithHardKeyboard(boolean show) {
1116            Settings.Secure.putIntForUser(mResolver, Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD,
1117                    show ? 1 : 0, mCurrentUserId);
1118        }
1119
1120        public int getCurrentUserId() {
1121            return mCurrentUserId;
1122        }
1123
1124        public int getSelectedInputMethodSubtypeId(String selectedImiId) {
1125            final InputMethodInfo imi = mMethodMap.get(selectedImiId);
1126            if (imi == null) {
1127                return NOT_A_SUBTYPE_ID;
1128            }
1129            final int subtypeHashCode = getSelectedInputMethodSubtypeHashCode();
1130            return getSubtypeIdFromHashCode(imi, subtypeHashCode);
1131        }
1132
1133        public void saveCurrentInputMethodAndSubtypeToHistory(
1134                String curMethodId, InputMethodSubtype currentSubtype) {
1135            String subtypeId = NOT_A_SUBTYPE_ID_STR;
1136            if (currentSubtype != null) {
1137                subtypeId = String.valueOf(currentSubtype.hashCode());
1138            }
1139            if (canAddToLastInputMethod(currentSubtype)) {
1140                addSubtypeToHistory(curMethodId, subtypeId);
1141            }
1142        }
1143
1144        public HashMap<InputMethodInfo, List<InputMethodSubtype>>
1145                getExplicitlyOrImplicitlyEnabledInputMethodsAndSubtypeListLocked(Context context) {
1146            HashMap<InputMethodInfo, List<InputMethodSubtype>> enabledInputMethodAndSubtypes =
1147                    new HashMap<InputMethodInfo, List<InputMethodSubtype>>();
1148            for (InputMethodInfo imi: getEnabledInputMethodListLocked()) {
1149                enabledInputMethodAndSubtypes.put(
1150                        imi, getEnabledInputMethodSubtypeListLocked(context, imi, true));
1151            }
1152            return enabledInputMethodAndSubtypes;
1153        }
1154    }
1155}
1156