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 
17 package com.android.server.inputmethod;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.annotation.UserIdInt;
22 import android.app.AppOpsManager;
23 import android.content.ContentResolver;
24 import android.content.Context;
25 import android.content.pm.ApplicationInfo;
26 import android.content.pm.IPackageManager;
27 import android.content.pm.PackageManager;
28 import android.content.res.Resources;
29 import android.os.Build;
30 import android.os.LocaleList;
31 import android.os.RemoteException;
32 import android.os.UserHandle;
33 import android.os.UserManagerInternal;
34 import android.provider.Settings;
35 import android.text.TextUtils;
36 import android.util.ArrayMap;
37 import android.util.ArraySet;
38 import android.util.IntArray;
39 import android.util.Pair;
40 import android.util.Printer;
41 import android.util.Slog;
42 import android.view.inputmethod.InputMethodInfo;
43 import android.view.inputmethod.InputMethodSubtype;
44 import android.view.inputmethod.InputMethodSystemProperty;
45 import android.view.textservice.SpellCheckerInfo;
46 
47 import com.android.internal.annotations.GuardedBy;
48 import com.android.internal.annotations.VisibleForTesting;
49 import com.android.internal.inputmethod.StartInputFlags;
50 import com.android.server.LocalServices;
51 import com.android.server.textservices.TextServicesManagerInternal;
52 
53 import java.io.PrintWriter;
54 import java.util.ArrayList;
55 import java.util.Arrays;
56 import java.util.LinkedHashSet;
57 import java.util.List;
58 import java.util.Locale;
59 
60 /**
61  * This class provides random static utility methods for {@link InputMethodManagerService} and its
62  * utility classes.
63  *
64  * <p>This class is intentionally package-private.  Utility methods here are tightly coupled with
65  * implementation details in {@link InputMethodManagerService}.  Hence this class is not suitable
66  * for other components to directly use.</p>
67  */
68 final class InputMethodUtils {
69     public static final boolean DEBUG = false;
70     static final int NOT_A_SUBTYPE_ID = -1;
71     private static final String SUBTYPE_MODE_ANY = null;
72     static final String SUBTYPE_MODE_KEYBOARD = "keyboard";
73     private static final String TAG = "InputMethodUtils";
74     private static final Locale ENGLISH_LOCALE = new Locale("en");
75     private static final String NOT_A_SUBTYPE_ID_STR = String.valueOf(NOT_A_SUBTYPE_ID);
76     private static final String TAG_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE =
77             "EnabledWhenDefaultIsNotAsciiCapable";
78 
79     // The string for enabled input method is saved as follows:
80     // example: ("ime0;subtype0;subtype1;subtype2:ime1:ime2;subtype0")
81     private static final char INPUT_METHOD_SEPARATOR = ':';
82     private static final char INPUT_METHOD_SUBTYPE_SEPARATOR = ';';
83     /**
84      * Used in {@link #getFallbackLocaleForDefaultIme(ArrayList, Context)} to find the fallback IMEs
85      * that are mainly used until the system becomes ready. Note that {@link Locale} in this array
86      * is checked with {@link Locale#equals(Object)}, which means that {@code Locale.ENGLISH}
87      * doesn't automatically match {@code Locale("en", "IN")}.
88      */
89     private static final Locale[] SEARCH_ORDER_OF_FALLBACK_LOCALES = {
90         Locale.ENGLISH, // "en"
91         Locale.US, // "en_US"
92         Locale.UK, // "en_GB"
93     };
94 
95     // A temporary workaround for the performance concerns in
96     // #getImplicitlyApplicableSubtypesLocked(Resources, InputMethodInfo).
97     // TODO: Optimize all the critical paths including this one.
98     private static final Object sCacheLock = new Object();
99     @GuardedBy("sCacheLock")
100     private static LocaleList sCachedSystemLocales;
101     @GuardedBy("sCacheLock")
102     private static InputMethodInfo sCachedInputMethodInfo;
103     @GuardedBy("sCacheLock")
104     private static ArrayList<InputMethodSubtype> sCachedResult;
105 
InputMethodUtils()106     private InputMethodUtils() {
107         // This utility class is not publicly instantiable.
108     }
109 
110     // ----------------------------------------------------------------------
111     // Utilities for debug
getApiCallStack()112     static String getApiCallStack() {
113         String apiCallStack = "";
114         try {
115             throw new RuntimeException();
116         } catch (RuntimeException e) {
117             final StackTraceElement[] frames = e.getStackTrace();
118             for (int j = 1; j < frames.length; ++j) {
119                 final String tempCallStack = frames[j].toString();
120                 if (TextUtils.isEmpty(apiCallStack)) {
121                     // Overwrite apiCallStack if it's empty
122                     apiCallStack = tempCallStack;
123                 } else if (tempCallStack.indexOf("Transact(") < 0) {
124                     // Overwrite apiCallStack if it's not a binder call
125                     apiCallStack = tempCallStack;
126                 } else {
127                     break;
128                 }
129             }
130         }
131         return apiCallStack;
132     }
133     // ----------------------------------------------------------------------
134 
isSystemImeThatHasSubtypeOf(InputMethodInfo imi, Context context, boolean checkDefaultAttribute, @Nullable Locale requiredLocale, boolean checkCountry, String requiredSubtypeMode)135     private static boolean isSystemImeThatHasSubtypeOf(InputMethodInfo imi, Context context,
136             boolean checkDefaultAttribute, @Nullable Locale requiredLocale, boolean checkCountry,
137             String requiredSubtypeMode) {
138         if (!imi.isSystem()) {
139             return false;
140         }
141         if (checkDefaultAttribute && !imi.isDefault(context)) {
142             return false;
143         }
144         if (!containsSubtypeOf(imi, requiredLocale, checkCountry, requiredSubtypeMode)) {
145             return false;
146         }
147         return true;
148     }
149 
150     @Nullable
getFallbackLocaleForDefaultIme(ArrayList<InputMethodInfo> imis, Context context)151     private static Locale getFallbackLocaleForDefaultIme(ArrayList<InputMethodInfo> imis,
152             Context context) {
153         // At first, find the fallback locale from the IMEs that are declared as "default" in the
154         // current locale.  Note that IME developers can declare an IME as "default" only for
155         // some particular locales but "not default" for other locales.
156         for (final Locale fallbackLocale : SEARCH_ORDER_OF_FALLBACK_LOCALES) {
157             for (int i = 0; i < imis.size(); ++i) {
158                 if (isSystemImeThatHasSubtypeOf(imis.get(i), context,
159                         true /* checkDefaultAttribute */, fallbackLocale,
160                         true /* checkCountry */, SUBTYPE_MODE_KEYBOARD)) {
161                     return fallbackLocale;
162                 }
163             }
164         }
165         // If no fallback locale is found in the above condition, find fallback locales regardless
166         // of the "default" attribute as a last resort.
167         for (final Locale fallbackLocale : SEARCH_ORDER_OF_FALLBACK_LOCALES) {
168             for (int i = 0; i < imis.size(); ++i) {
169                 if (isSystemImeThatHasSubtypeOf(imis.get(i), context,
170                         false /* checkDefaultAttribute */, fallbackLocale,
171                         true /* checkCountry */, SUBTYPE_MODE_KEYBOARD)) {
172                     return fallbackLocale;
173                 }
174             }
175         }
176         Slog.w(TAG, "Found no fallback locale. imis=" + Arrays.toString(imis.toArray()));
177         return null;
178     }
179 
isSystemAuxilialyImeThatHasAutomaticSubtype(InputMethodInfo imi, Context context, boolean checkDefaultAttribute)180     private static boolean isSystemAuxilialyImeThatHasAutomaticSubtype(InputMethodInfo imi,
181             Context context, boolean checkDefaultAttribute) {
182         if (!imi.isSystem()) {
183             return false;
184         }
185         if (checkDefaultAttribute && !imi.isDefault(context)) {
186             return false;
187         }
188         if (!imi.isAuxiliaryIme()) {
189             return false;
190         }
191         final int subtypeCount = imi.getSubtypeCount();
192         for (int i = 0; i < subtypeCount; ++i) {
193             final InputMethodSubtype s = imi.getSubtypeAt(i);
194             if (s.overridesImplicitlyEnabledSubtype()) {
195                 return true;
196             }
197         }
198         return false;
199     }
200 
getSystemLocaleFromContext(Context context)201     private static Locale getSystemLocaleFromContext(Context context) {
202         try {
203             return context.getResources().getConfiguration().locale;
204         } catch (Resources.NotFoundException ex) {
205             return null;
206         }
207     }
208 
209     private static final class InputMethodListBuilder {
210         // Note: We use LinkedHashSet instead of android.util.ArraySet because the enumeration
211         // order can have non-trivial effect in the call sites.
212         @NonNull
213         private final LinkedHashSet<InputMethodInfo> mInputMethodSet = new LinkedHashSet<>();
214 
fillImes(ArrayList<InputMethodInfo> imis, Context context, boolean checkDefaultAttribute, @Nullable Locale locale, boolean checkCountry, String requiredSubtypeMode)215         InputMethodListBuilder fillImes(ArrayList<InputMethodInfo> imis, Context context,
216                 boolean checkDefaultAttribute, @Nullable Locale locale, boolean checkCountry,
217                 String requiredSubtypeMode) {
218             for (int i = 0; i < imis.size(); ++i) {
219                 final InputMethodInfo imi = imis.get(i);
220                 if (isSystemImeThatHasSubtypeOf(imi, context, checkDefaultAttribute, locale,
221                         checkCountry, requiredSubtypeMode)) {
222                     mInputMethodSet.add(imi);
223                 }
224             }
225             return this;
226         }
227 
228         // TODO: The behavior of InputMethodSubtype#overridesImplicitlyEnabledSubtype() should be
229         // documented more clearly.
fillAuxiliaryImes(ArrayList<InputMethodInfo> imis, Context context)230         InputMethodListBuilder fillAuxiliaryImes(ArrayList<InputMethodInfo> imis, Context context) {
231             // If one or more auxiliary input methods are available, OK to stop populating the list.
232             for (final InputMethodInfo imi : mInputMethodSet) {
233                 if (imi.isAuxiliaryIme()) {
234                     return this;
235                 }
236             }
237             boolean added = false;
238             for (int i = 0; i < imis.size(); ++i) {
239                 final InputMethodInfo imi = imis.get(i);
240                 if (isSystemAuxilialyImeThatHasAutomaticSubtype(imi, context,
241                         true /* checkDefaultAttribute */)) {
242                     mInputMethodSet.add(imi);
243                     added = true;
244                 }
245             }
246             if (added) {
247                 return this;
248             }
249             for (int i = 0; i < imis.size(); ++i) {
250                 final InputMethodInfo imi = imis.get(i);
251                 if (isSystemAuxilialyImeThatHasAutomaticSubtype(imi, context,
252                         false /* checkDefaultAttribute */)) {
253                     mInputMethodSet.add(imi);
254                 }
255             }
256             return this;
257         }
258 
isEmpty()259         public boolean isEmpty() {
260             return mInputMethodSet.isEmpty();
261         }
262 
263         @NonNull
build()264         public ArrayList<InputMethodInfo> build() {
265             return new ArrayList<>(mInputMethodSet);
266         }
267     }
268 
getMinimumKeyboardSetWithSystemLocale( ArrayList<InputMethodInfo> imis, Context context, @Nullable Locale systemLocale, @Nullable Locale fallbackLocale)269     private static InputMethodListBuilder getMinimumKeyboardSetWithSystemLocale(
270             ArrayList<InputMethodInfo> imis, Context context, @Nullable Locale systemLocale,
271             @Nullable Locale fallbackLocale) {
272         // Once the system becomes ready, we pick up at least one keyboard in the following order.
273         // Secondary users fall into this category in general.
274         // 1. checkDefaultAttribute: true, locale: systemLocale, checkCountry: true
275         // 2. checkDefaultAttribute: true, locale: systemLocale, checkCountry: false
276         // 3. checkDefaultAttribute: true, locale: fallbackLocale, checkCountry: true
277         // 4. checkDefaultAttribute: true, locale: fallbackLocale, checkCountry: false
278         // 5. checkDefaultAttribute: false, locale: fallbackLocale, checkCountry: true
279         // 6. checkDefaultAttribute: false, locale: fallbackLocale, checkCountry: false
280         // TODO: We should check isAsciiCapable instead of relying on fallbackLocale.
281 
282         final InputMethodListBuilder builder = new InputMethodListBuilder();
283         builder.fillImes(imis, context, true /* checkDefaultAttribute */, systemLocale,
284                 true /* checkCountry */, SUBTYPE_MODE_KEYBOARD);
285         if (!builder.isEmpty()) {
286             return builder;
287         }
288         builder.fillImes(imis, context, true /* checkDefaultAttribute */, systemLocale,
289                 false /* checkCountry */, SUBTYPE_MODE_KEYBOARD);
290         if (!builder.isEmpty()) {
291             return builder;
292         }
293         builder.fillImes(imis, context, true /* checkDefaultAttribute */, fallbackLocale,
294                 true /* checkCountry */, SUBTYPE_MODE_KEYBOARD);
295         if (!builder.isEmpty()) {
296             return builder;
297         }
298         builder.fillImes(imis, context, true /* checkDefaultAttribute */, fallbackLocale,
299                 false /* checkCountry */, SUBTYPE_MODE_KEYBOARD);
300         if (!builder.isEmpty()) {
301             return builder;
302         }
303         builder.fillImes(imis, context, false /* checkDefaultAttribute */, fallbackLocale,
304                 true /* checkCountry */, SUBTYPE_MODE_KEYBOARD);
305         if (!builder.isEmpty()) {
306             return builder;
307         }
308         builder.fillImes(imis, context, false /* checkDefaultAttribute */, fallbackLocale,
309                 false /* checkCountry */, SUBTYPE_MODE_KEYBOARD);
310         if (!builder.isEmpty()) {
311             return builder;
312         }
313         Slog.w(TAG, "No software keyboard is found. imis=" + Arrays.toString(imis.toArray())
314                 + " systemLocale=" + systemLocale + " fallbackLocale=" + fallbackLocale);
315         return builder;
316     }
317 
getDefaultEnabledImes( Context context, ArrayList<InputMethodInfo> imis, boolean onlyMinimum)318     static ArrayList<InputMethodInfo> getDefaultEnabledImes(
319             Context context, ArrayList<InputMethodInfo> imis, boolean onlyMinimum) {
320         final Locale fallbackLocale = getFallbackLocaleForDefaultIme(imis, context);
321         // We will primarily rely on the system locale, but also keep relying on the fallback locale
322         // as a last resort.
323         // Also pick up suitable IMEs regardless of the software keyboard support (e.g. Voice IMEs),
324         // then pick up suitable auxiliary IMEs when necessary (e.g. Voice IMEs with "automatic"
325         // subtype)
326         final Locale systemLocale = getSystemLocaleFromContext(context);
327         final InputMethodListBuilder builder =
328                 getMinimumKeyboardSetWithSystemLocale(imis, context, systemLocale, fallbackLocale);
329         if (!onlyMinimum) {
330             builder.fillImes(imis, context, true /* checkDefaultAttribute */, systemLocale,
331                     true /* checkCountry */, SUBTYPE_MODE_ANY)
332                     .fillAuxiliaryImes(imis, context);
333         }
334         return builder.build();
335     }
336 
getDefaultEnabledImes( Context context, ArrayList<InputMethodInfo> imis)337     static ArrayList<InputMethodInfo> getDefaultEnabledImes(
338             Context context, ArrayList<InputMethodInfo> imis) {
339         return getDefaultEnabledImes(context, imis, false /* onlyMinimum */);
340     }
341 
containsSubtypeOf(InputMethodInfo imi, @Nullable Locale locale, boolean checkCountry, String mode)342     static boolean containsSubtypeOf(InputMethodInfo imi, @Nullable Locale locale,
343             boolean checkCountry, String mode) {
344         if (locale == null) {
345             return false;
346         }
347         final int N = imi.getSubtypeCount();
348         for (int i = 0; i < N; ++i) {
349             final InputMethodSubtype subtype = imi.getSubtypeAt(i);
350             if (checkCountry) {
351                 final Locale subtypeLocale = subtype.getLocaleObject();
352                 if (subtypeLocale == null ||
353                         !TextUtils.equals(subtypeLocale.getLanguage(), locale.getLanguage()) ||
354                         !TextUtils.equals(subtypeLocale.getCountry(), locale.getCountry())) {
355                     continue;
356                 }
357             } else {
358                 final Locale subtypeLocale = new Locale(getLanguageFromLocaleString(
359                         subtype.getLocale()));
360                 if (!TextUtils.equals(subtypeLocale.getLanguage(), locale.getLanguage())) {
361                     continue;
362                 }
363             }
364             if (mode == SUBTYPE_MODE_ANY || TextUtils.isEmpty(mode) ||
365                     mode.equalsIgnoreCase(subtype.getMode())) {
366                 return true;
367             }
368         }
369         return false;
370     }
371 
getSubtypes(InputMethodInfo imi)372     static ArrayList<InputMethodSubtype> getSubtypes(InputMethodInfo imi) {
373         ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
374         final int subtypeCount = imi.getSubtypeCount();
375         for (int i = 0; i < subtypeCount; ++i) {
376             subtypes.add(imi.getSubtypeAt(i));
377         }
378         return subtypes;
379     }
380 
getMostApplicableDefaultIME(List<InputMethodInfo> enabledImes)381     static InputMethodInfo getMostApplicableDefaultIME(List<InputMethodInfo> enabledImes) {
382         if (enabledImes == null || enabledImes.isEmpty()) {
383             return null;
384         }
385         // We'd prefer to fall back on a system IME, since that is safer.
386         int i = enabledImes.size();
387         int firstFoundSystemIme = -1;
388         while (i > 0) {
389             i--;
390             final InputMethodInfo imi = enabledImes.get(i);
391             if (imi.isAuxiliaryIme()) {
392                 continue;
393             }
394             if (imi.isSystem() && containsSubtypeOf(
395                     imi, ENGLISH_LOCALE, false /* checkCountry */, SUBTYPE_MODE_KEYBOARD)) {
396                 return imi;
397             }
398             if (firstFoundSystemIme < 0 && imi.isSystem()) {
399                 firstFoundSystemIme = i;
400             }
401         }
402         return enabledImes.get(Math.max(firstFoundSystemIme, 0));
403     }
404 
isValidSubtypeId(InputMethodInfo imi, int subtypeHashCode)405     static boolean isValidSubtypeId(InputMethodInfo imi, int subtypeHashCode) {
406         return getSubtypeIdFromHashCode(imi, subtypeHashCode) != NOT_A_SUBTYPE_ID;
407     }
408 
getSubtypeIdFromHashCode(InputMethodInfo imi, int subtypeHashCode)409     static int getSubtypeIdFromHashCode(InputMethodInfo imi, int subtypeHashCode) {
410         if (imi != null) {
411             final int subtypeCount = imi.getSubtypeCount();
412             for (int i = 0; i < subtypeCount; ++i) {
413                 InputMethodSubtype ims = imi.getSubtypeAt(i);
414                 if (subtypeHashCode == ims.hashCode()) {
415                     return i;
416                 }
417             }
418         }
419         return NOT_A_SUBTYPE_ID;
420     }
421 
422     private static final LocaleUtils.LocaleExtractor<InputMethodSubtype> sSubtypeToLocale =
423             new LocaleUtils.LocaleExtractor<InputMethodSubtype>() {
424                 @Override
425                 public Locale get(InputMethodSubtype source) {
426                     return source != null ? source.getLocaleObject() : null;
427                 }
428             };
429 
430     @VisibleForTesting
getImplicitlyApplicableSubtypesLocked( Resources res, InputMethodInfo imi)431     static ArrayList<InputMethodSubtype> getImplicitlyApplicableSubtypesLocked(
432             Resources res, InputMethodInfo imi) {
433         final LocaleList systemLocales = res.getConfiguration().getLocales();
434 
435         synchronized (sCacheLock) {
436             // We intentionally do not use InputMethodInfo#equals(InputMethodInfo) here because
437             // it does not check if subtypes are also identical.
438             if (systemLocales.equals(sCachedSystemLocales) && sCachedInputMethodInfo == imi) {
439                 return new ArrayList<>(sCachedResult);
440             }
441         }
442 
443         // Note: Only resource info in "res" is used in getImplicitlyApplicableSubtypesLockedImpl().
444         // TODO: Refactor getImplicitlyApplicableSubtypesLockedImpl() so that it can receive
445         // LocaleList rather than Resource.
446         final ArrayList<InputMethodSubtype> result =
447                 getImplicitlyApplicableSubtypesLockedImpl(res, imi);
448         synchronized (sCacheLock) {
449             // Both LocaleList and InputMethodInfo are immutable. No need to copy them here.
450             sCachedSystemLocales = systemLocales;
451             sCachedInputMethodInfo = imi;
452             sCachedResult = new ArrayList<>(result);
453         }
454         return result;
455     }
456 
getImplicitlyApplicableSubtypesLockedImpl( Resources res, InputMethodInfo imi)457     private static ArrayList<InputMethodSubtype> getImplicitlyApplicableSubtypesLockedImpl(
458             Resources res, InputMethodInfo imi) {
459         final List<InputMethodSubtype> subtypes = InputMethodUtils.getSubtypes(imi);
460         final LocaleList systemLocales = res.getConfiguration().getLocales();
461         final String systemLocale = systemLocales.get(0).toString();
462         if (TextUtils.isEmpty(systemLocale)) return new ArrayList<>();
463         final int numSubtypes = subtypes.size();
464 
465         // Handle overridesImplicitlyEnabledSubtype mechanism.
466         final ArrayMap<String, InputMethodSubtype> applicableModeAndSubtypesMap = new ArrayMap<>();
467         for (int i = 0; i < numSubtypes; ++i) {
468             // scan overriding implicitly enabled subtypes.
469             final InputMethodSubtype subtype = subtypes.get(i);
470             if (subtype.overridesImplicitlyEnabledSubtype()) {
471                 final String mode = subtype.getMode();
472                 if (!applicableModeAndSubtypesMap.containsKey(mode)) {
473                     applicableModeAndSubtypesMap.put(mode, subtype);
474                 }
475             }
476         }
477         if (applicableModeAndSubtypesMap.size() > 0) {
478             return new ArrayList<>(applicableModeAndSubtypesMap.values());
479         }
480 
481         final ArrayMap<String, ArrayList<InputMethodSubtype>> nonKeyboardSubtypesMap =
482                 new ArrayMap<>();
483         final ArrayList<InputMethodSubtype> keyboardSubtypes = new ArrayList<>();
484 
485         for (int i = 0; i < numSubtypes; ++i) {
486             final InputMethodSubtype subtype = subtypes.get(i);
487             final String mode = subtype.getMode();
488             if (SUBTYPE_MODE_KEYBOARD.equals(mode)) {
489                 keyboardSubtypes.add(subtype);
490             } else {
491                 if (!nonKeyboardSubtypesMap.containsKey(mode)) {
492                     nonKeyboardSubtypesMap.put(mode, new ArrayList<>());
493                 }
494                 nonKeyboardSubtypesMap.get(mode).add(subtype);
495             }
496         }
497 
498         final ArrayList<InputMethodSubtype> applicableSubtypes = new ArrayList<>();
499         LocaleUtils.filterByLanguage(keyboardSubtypes, sSubtypeToLocale, systemLocales,
500                 applicableSubtypes);
501 
502         if (!applicableSubtypes.isEmpty()) {
503             boolean hasAsciiCapableKeyboard = false;
504             final int numApplicationSubtypes = applicableSubtypes.size();
505             for (int i = 0; i < numApplicationSubtypes; ++i) {
506                 final InputMethodSubtype subtype = applicableSubtypes.get(i);
507                 if (subtype.isAsciiCapable()) {
508                     hasAsciiCapableKeyboard = true;
509                     break;
510                 }
511             }
512             if (!hasAsciiCapableKeyboard) {
513                 final int numKeyboardSubtypes = keyboardSubtypes.size();
514                 for (int i = 0; i < numKeyboardSubtypes; ++i) {
515                     final InputMethodSubtype subtype = keyboardSubtypes.get(i);
516                     final String mode = subtype.getMode();
517                     if (SUBTYPE_MODE_KEYBOARD.equals(mode) && subtype.containsExtraValueKey(
518                             TAG_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE)) {
519                         applicableSubtypes.add(subtype);
520                     }
521                 }
522             }
523         }
524 
525         if (applicableSubtypes.isEmpty()) {
526             InputMethodSubtype lastResortKeyboardSubtype = findLastResortApplicableSubtypeLocked(
527                     res, subtypes, SUBTYPE_MODE_KEYBOARD, systemLocale, true);
528             if (lastResortKeyboardSubtype != null) {
529                 applicableSubtypes.add(lastResortKeyboardSubtype);
530             }
531         }
532 
533         // For each non-keyboard mode, extract subtypes with system locales.
534         for (final ArrayList<InputMethodSubtype> subtypeList : nonKeyboardSubtypesMap.values()) {
535             LocaleUtils.filterByLanguage(subtypeList, sSubtypeToLocale, systemLocales,
536                     applicableSubtypes);
537         }
538 
539         return applicableSubtypes;
540     }
541 
542     /**
543      * Returns the language component of a given locale string.
544      * TODO: Use {@link Locale#toLanguageTag()} and {@link Locale#forLanguageTag(String)}
545      */
getLanguageFromLocaleString(String locale)546     private static String getLanguageFromLocaleString(String locale) {
547         final int idx = locale.indexOf('_');
548         if (idx < 0) {
549             return locale;
550         } else {
551             return locale.substring(0, idx);
552         }
553     }
554 
555     /**
556      * If there are no selected subtypes, tries finding the most applicable one according to the
557      * given locale.
558      * @param subtypes this function will search the most applicable subtype in subtypes
559      * @param mode subtypes will be filtered by mode
560      * @param locale subtypes will be filtered by locale
561      * @param canIgnoreLocaleAsLastResort if this function can't find the most applicable subtype,
562      * it will return the first subtype matched with mode
563      * @return the most applicable subtypeId
564      */
findLastResortApplicableSubtypeLocked( Resources res, List<InputMethodSubtype> subtypes, String mode, String locale, boolean canIgnoreLocaleAsLastResort)565     static InputMethodSubtype findLastResortApplicableSubtypeLocked(
566             Resources res, List<InputMethodSubtype> subtypes, String mode, String locale,
567             boolean canIgnoreLocaleAsLastResort) {
568         if (subtypes == null || subtypes.size() == 0) {
569             return null;
570         }
571         if (TextUtils.isEmpty(locale)) {
572             locale = res.getConfiguration().locale.toString();
573         }
574         final String language = getLanguageFromLocaleString(locale);
575         boolean partialMatchFound = false;
576         InputMethodSubtype applicableSubtype = null;
577         InputMethodSubtype firstMatchedModeSubtype = null;
578         final int N = subtypes.size();
579         for (int i = 0; i < N; ++i) {
580             InputMethodSubtype subtype = subtypes.get(i);
581             final String subtypeLocale = subtype.getLocale();
582             final String subtypeLanguage = getLanguageFromLocaleString(subtypeLocale);
583             // An applicable subtype should match "mode". If mode is null, mode will be ignored,
584             // and all subtypes with all modes can be candidates.
585             if (mode == null || subtypes.get(i).getMode().equalsIgnoreCase(mode)) {
586                 if (firstMatchedModeSubtype == null) {
587                     firstMatchedModeSubtype = subtype;
588                 }
589                 if (locale.equals(subtypeLocale)) {
590                     // Exact match (e.g. system locale is "en_US" and subtype locale is "en_US")
591                     applicableSubtype = subtype;
592                     break;
593                 } else if (!partialMatchFound && language.equals(subtypeLanguage)) {
594                     // Partial match (e.g. system locale is "en_US" and subtype locale is "en")
595                     applicableSubtype = subtype;
596                     partialMatchFound = true;
597                 }
598             }
599         }
600 
601         if (applicableSubtype == null && canIgnoreLocaleAsLastResort) {
602             return firstMatchedModeSubtype;
603         }
604 
605         // The first subtype applicable to the system locale will be defined as the most applicable
606         // subtype.
607         if (DEBUG) {
608             if (applicableSubtype != null) {
609                 Slog.d(TAG, "Applicable InputMethodSubtype was found: "
610                         + applicableSubtype.getMode() + "," + applicableSubtype.getLocale());
611             }
612         }
613         return applicableSubtype;
614     }
615 
canAddToLastInputMethod(InputMethodSubtype subtype)616     static boolean canAddToLastInputMethod(InputMethodSubtype subtype) {
617         if (subtype == null) return true;
618         return !subtype.isAuxiliary();
619     }
620 
setNonSelectedSystemImesDisabledUntilUsed(IPackageManager packageManager, List<InputMethodInfo> enabledImis, @UserIdInt int userId, String callingPackage)621     static void setNonSelectedSystemImesDisabledUntilUsed(IPackageManager packageManager,
622             List<InputMethodInfo> enabledImis, @UserIdInt int userId, String callingPackage) {
623         if (DEBUG) {
624             Slog.d(TAG, "setNonSelectedSystemImesDisabledUntilUsed");
625         }
626         final String[] systemImesDisabledUntilUsed = Resources.getSystem().getStringArray(
627                 com.android.internal.R.array.config_disabledUntilUsedPreinstalledImes);
628         if (systemImesDisabledUntilUsed == null || systemImesDisabledUntilUsed.length == 0) {
629             return;
630         }
631         // Only the current spell checker should be treated as an enabled one.
632         final SpellCheckerInfo currentSpellChecker =
633                 TextServicesManagerInternal.get().getCurrentSpellCheckerForUser(userId);
634         for (final String packageName : systemImesDisabledUntilUsed) {
635             if (DEBUG) {
636                 Slog.d(TAG, "check " + packageName);
637             }
638             boolean enabledIme = false;
639             for (int j = 0; j < enabledImis.size(); ++j) {
640                 final InputMethodInfo imi = enabledImis.get(j);
641                 if (packageName.equals(imi.getPackageName())) {
642                     enabledIme = true;
643                     break;
644                 }
645             }
646             if (enabledIme) {
647                 // enabled ime. skip
648                 continue;
649             }
650             if (currentSpellChecker != null
651                     && packageName.equals(currentSpellChecker.getPackageName())) {
652                 // enabled spell checker. skip
653                 if (DEBUG) {
654                     Slog.d(TAG, packageName + " is the current spell checker. skip");
655                 }
656                 continue;
657             }
658             ApplicationInfo ai = null;
659             try {
660                 ai = packageManager.getApplicationInfo(packageName,
661                         PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS, userId);
662             } catch (RemoteException e) {
663                 Slog.w(TAG, "getApplicationInfo failed. packageName=" + packageName
664                         + " userId=" + userId, e);
665                 continue;
666             }
667             if (ai == null) {
668                 // No app found for packageName
669                 continue;
670             }
671             final boolean isSystemPackage = (ai.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
672             if (!isSystemPackage) {
673                 continue;
674             }
675             setDisabledUntilUsed(packageManager, packageName, userId, callingPackage);
676         }
677     }
678 
setDisabledUntilUsed(IPackageManager packageManager, String packageName, int userId, String callingPackage)679     private static void setDisabledUntilUsed(IPackageManager packageManager, String packageName,
680             int userId, String callingPackage) {
681         final int state;
682         try {
683             state = packageManager.getApplicationEnabledSetting(packageName, userId);
684         } catch (RemoteException e) {
685             Slog.w(TAG, "getApplicationEnabledSetting failed. packageName=" + packageName
686                     + " userId=" + userId, e);
687             return;
688         }
689         if (state == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
690                 || state == PackageManager.COMPONENT_ENABLED_STATE_ENABLED) {
691             if (DEBUG) {
692                 Slog.d(TAG, "Update state(" + packageName + "): DISABLED_UNTIL_USED");
693             }
694             try {
695                 packageManager.setApplicationEnabledSetting(packageName,
696                         PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED,
697                         0 /* newState */, userId, callingPackage);
698             } catch (RemoteException e) {
699                 Slog.w(TAG, "setApplicationEnabledSetting failed. packageName=" + packageName
700                         + " userId=" + userId + " callingPackage=" + callingPackage, e);
701                 return;
702             }
703         } else {
704             if (DEBUG) {
705                 Slog.d(TAG, packageName + " is already DISABLED_UNTIL_USED");
706             }
707         }
708     }
709 
getImeAndSubtypeDisplayName(Context context, InputMethodInfo imi, InputMethodSubtype subtype)710     static CharSequence getImeAndSubtypeDisplayName(Context context, InputMethodInfo imi,
711             InputMethodSubtype subtype) {
712         final CharSequence imiLabel = imi.loadLabel(context.getPackageManager());
713         return subtype != null
714                 ? TextUtils.concat(subtype.getDisplayName(context,
715                         imi.getPackageName(), imi.getServiceInfo().applicationInfo),
716                                 (TextUtils.isEmpty(imiLabel) ?
717                                         "" : " - " + imiLabel))
718                 : imiLabel;
719     }
720 
721     /**
722      * Returns true if a package name belongs to a UID.
723      *
724      * <p>This is a simple wrapper of {@link AppOpsManager#checkPackage(int, String)}.</p>
725      * @param appOpsManager the {@link AppOpsManager} object to be used for the validation.
726      * @param uid the UID to be validated.
727      * @param packageName the package name.
728      * @return {@code true} if the package name belongs to the UID.
729      */
checkIfPackageBelongsToUid(AppOpsManager appOpsManager, @UserIdInt int uid, String packageName)730     static boolean checkIfPackageBelongsToUid(AppOpsManager appOpsManager,
731             @UserIdInt int uid, String packageName) {
732         try {
733             appOpsManager.checkPackage(uid, packageName);
734             return true;
735         } catch (SecurityException e) {
736             return false;
737         }
738     }
739 
740     /**
741      * Utility class for putting and getting settings for InputMethod
742      * TODO: Move all putters and getters of settings to this class.
743      */
744     public static class InputMethodSettings {
745         private final TextUtils.SimpleStringSplitter mInputMethodSplitter =
746                 new TextUtils.SimpleStringSplitter(INPUT_METHOD_SEPARATOR);
747 
748         private final TextUtils.SimpleStringSplitter mSubtypeSplitter =
749                 new TextUtils.SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATOR);
750 
751         private final Resources mRes;
752         private final ContentResolver mResolver;
753         private final ArrayMap<String, InputMethodInfo> mMethodMap;
754 
755         /**
756          * On-memory data store to emulate when {@link #mCopyOnWrite} is {@code true}.
757          */
758         private final ArrayMap<String, String> mCopyOnWriteDataStore = new ArrayMap<>();
759 
760         private static final ArraySet<String> CLONE_TO_MANAGED_PROFILE = new ArraySet<>();
761         static {
762             Settings.Secure.getCloneToManagedProfileSettings(CLONE_TO_MANAGED_PROFILE);
763         }
764 
765         private static final UserManagerInternal sUserManagerInternal =
766                 LocalServices.getService(UserManagerInternal.class);
767 
768         private boolean mCopyOnWrite = false;
769         @NonNull
770         private String mEnabledInputMethodsStrCache = "";
771         @UserIdInt
772         private int mCurrentUserId;
773         private int[] mCurrentProfileIds = new int[0];
774 
buildEnabledInputMethodsSettingString( StringBuilder builder, Pair<String, ArrayList<String>> ime)775         private static void buildEnabledInputMethodsSettingString(
776                 StringBuilder builder, Pair<String, ArrayList<String>> ime) {
777             builder.append(ime.first);
778             // Inputmethod and subtypes are saved in the settings as follows:
779             // ime0;subtype0;subtype1:ime1;subtype0:ime2:ime3;subtype0;subtype1
780             for (String subtypeId: ime.second) {
781                 builder.append(INPUT_METHOD_SUBTYPE_SEPARATOR).append(subtypeId);
782             }
783         }
784 
buildInputMethodsAndSubtypeList( String enabledInputMethodsStr, TextUtils.SimpleStringSplitter inputMethodSplitter, TextUtils.SimpleStringSplitter subtypeSplitter)785         private static List<Pair<String, ArrayList<String>>> buildInputMethodsAndSubtypeList(
786                 String enabledInputMethodsStr,
787                 TextUtils.SimpleStringSplitter inputMethodSplitter,
788                 TextUtils.SimpleStringSplitter subtypeSplitter) {
789             ArrayList<Pair<String, ArrayList<String>>> imsList = new ArrayList<>();
790             if (TextUtils.isEmpty(enabledInputMethodsStr)) {
791                 return imsList;
792             }
793             inputMethodSplitter.setString(enabledInputMethodsStr);
794             while (inputMethodSplitter.hasNext()) {
795                 String nextImsStr = inputMethodSplitter.next();
796                 subtypeSplitter.setString(nextImsStr);
797                 if (subtypeSplitter.hasNext()) {
798                     ArrayList<String> subtypeHashes = new ArrayList<>();
799                     // The first element is ime id.
800                     String imeId = subtypeSplitter.next();
801                     while (subtypeSplitter.hasNext()) {
802                         subtypeHashes.add(subtypeSplitter.next());
803                     }
804                     imsList.add(new Pair<>(imeId, subtypeHashes));
805                 }
806             }
807             return imsList;
808         }
809 
InputMethodSettings(Resources res, ContentResolver resolver, ArrayMap<String, InputMethodInfo> methodMap, @UserIdInt int userId, boolean copyOnWrite)810         InputMethodSettings(Resources res, ContentResolver resolver,
811                 ArrayMap<String, InputMethodInfo> methodMap, @UserIdInt int userId,
812                 boolean copyOnWrite) {
813             mRes = res;
814             mResolver = resolver;
815             mMethodMap = methodMap;
816             switchCurrentUser(userId, copyOnWrite);
817         }
818 
819         /**
820          * Must be called when the current user is changed.
821          *
822          * @param userId The user ID.
823          * @param copyOnWrite If {@code true}, for each settings key
824          * (e.g. {@link Settings.Secure#ACTION_INPUT_METHOD_SUBTYPE_SETTINGS}) we use the actual
825          * settings on the {@link Settings.Secure} until we do the first write operation.
826          */
switchCurrentUser(@serIdInt int userId, boolean copyOnWrite)827         void switchCurrentUser(@UserIdInt int userId, boolean copyOnWrite) {
828             if (DEBUG) {
829                 Slog.d(TAG, "--- Switch the current user from " + mCurrentUserId + " to " + userId);
830             }
831             if (mCurrentUserId != userId || mCopyOnWrite != copyOnWrite) {
832                 mCopyOnWriteDataStore.clear();
833                 mEnabledInputMethodsStrCache = "";
834                 // TODO: mCurrentProfileIds should be cleared here.
835             }
836             mCurrentUserId = userId;
837             mCopyOnWrite = copyOnWrite;
838             // TODO: mCurrentProfileIds should be updated here.
839         }
840 
putString(@onNull String key, @Nullable String str)841         private void putString(@NonNull String key, @Nullable String str) {
842             if (mCopyOnWrite) {
843                 mCopyOnWriteDataStore.put(key, str);
844             } else {
845                 final int userId = CLONE_TO_MANAGED_PROFILE.contains(key)
846                         ? sUserManagerInternal.getProfileParentId(mCurrentUserId) : mCurrentUserId;
847                 Settings.Secure.putStringForUser(mResolver, key, str, userId);
848             }
849         }
850 
851         @Nullable
getString(@onNull String key, @Nullable String defaultValue)852         private String getString(@NonNull String key, @Nullable String defaultValue) {
853             final String result;
854             if (mCopyOnWrite && mCopyOnWriteDataStore.containsKey(key)) {
855                 result = mCopyOnWriteDataStore.get(key);
856             } else {
857                 result = Settings.Secure.getStringForUser(mResolver, key, mCurrentUserId);
858             }
859             return result != null ? result : defaultValue;
860         }
861 
putInt(String key, int value)862         private void putInt(String key, int value) {
863             if (mCopyOnWrite) {
864                 mCopyOnWriteDataStore.put(key, String.valueOf(value));
865             } else {
866                 final int userId = CLONE_TO_MANAGED_PROFILE.contains(key)
867                         ? sUserManagerInternal.getProfileParentId(mCurrentUserId) : mCurrentUserId;
868                 Settings.Secure.putIntForUser(mResolver, key, value, userId);
869             }
870         }
871 
getInt(String key, int defaultValue)872         private int getInt(String key, int defaultValue) {
873             if (mCopyOnWrite && mCopyOnWriteDataStore.containsKey(key)) {
874                 final String result = mCopyOnWriteDataStore.get(key);
875                 return result != null ? Integer.parseInt(result) : defaultValue;
876             }
877             return Settings.Secure.getIntForUser(mResolver, key, defaultValue, mCurrentUserId);
878         }
879 
putBoolean(String key, boolean value)880         private void putBoolean(String key, boolean value) {
881             putInt(key, value ? 1 : 0);
882         }
883 
getBoolean(String key, boolean defaultValue)884         private boolean getBoolean(String key, boolean defaultValue) {
885             return getInt(key, defaultValue ? 1 : 0) == 1;
886         }
887 
setCurrentProfileIds(int[] currentProfileIds)888         public void setCurrentProfileIds(int[] currentProfileIds) {
889             synchronized (this) {
890                 mCurrentProfileIds = currentProfileIds;
891             }
892         }
893 
isCurrentProfile(int userId)894         public boolean isCurrentProfile(int userId) {
895             synchronized (this) {
896                 if (userId == mCurrentUserId) return true;
897                 for (int i = 0; i < mCurrentProfileIds.length; i++) {
898                     if (userId == mCurrentProfileIds[i]) return true;
899                 }
900                 return false;
901             }
902         }
903 
getEnabledInputMethodListLocked()904         ArrayList<InputMethodInfo> getEnabledInputMethodListLocked() {
905             return createEnabledInputMethodListLocked(
906                     getEnabledInputMethodsAndSubtypeListLocked());
907         }
908 
getEnabledInputMethodSubtypeListLocked( Context context, InputMethodInfo imi, boolean allowsImplicitlySelectedSubtypes)909         List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked(
910                 Context context, InputMethodInfo imi, boolean allowsImplicitlySelectedSubtypes) {
911             List<InputMethodSubtype> enabledSubtypes =
912                     getEnabledInputMethodSubtypeListLocked(imi);
913             if (allowsImplicitlySelectedSubtypes && enabledSubtypes.isEmpty()) {
914                 enabledSubtypes = InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
915                         context.getResources(), imi);
916             }
917             return InputMethodSubtype.sort(context, 0, imi, enabledSubtypes);
918         }
919 
getEnabledInputMethodSubtypeListLocked(InputMethodInfo imi)920         List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked(InputMethodInfo imi) {
921             List<Pair<String, ArrayList<String>>> imsList =
922                     getEnabledInputMethodsAndSubtypeListLocked();
923             ArrayList<InputMethodSubtype> enabledSubtypes = new ArrayList<>();
924             if (imi != null) {
925                 for (Pair<String, ArrayList<String>> imsPair : imsList) {
926                     InputMethodInfo info = mMethodMap.get(imsPair.first);
927                     if (info != null && info.getId().equals(imi.getId())) {
928                         final int subtypeCount = info.getSubtypeCount();
929                         for (int i = 0; i < subtypeCount; ++i) {
930                             InputMethodSubtype ims = info.getSubtypeAt(i);
931                             for (String s: imsPair.second) {
932                                 if (String.valueOf(ims.hashCode()).equals(s)) {
933                                     enabledSubtypes.add(ims);
934                                 }
935                             }
936                         }
937                         break;
938                     }
939                 }
940             }
941             return enabledSubtypes;
942         }
943 
getEnabledInputMethodsAndSubtypeListLocked()944         List<Pair<String, ArrayList<String>>> getEnabledInputMethodsAndSubtypeListLocked() {
945             return buildInputMethodsAndSubtypeList(getEnabledInputMethodsStr(),
946                     mInputMethodSplitter,
947                     mSubtypeSplitter);
948         }
949 
appendAndPutEnabledInputMethodLocked(String id, boolean reloadInputMethodStr)950         void appendAndPutEnabledInputMethodLocked(String id, boolean reloadInputMethodStr) {
951             if (reloadInputMethodStr) {
952                 getEnabledInputMethodsStr();
953             }
954             if (TextUtils.isEmpty(mEnabledInputMethodsStrCache)) {
955                 // Add in the newly enabled input method.
956                 putEnabledInputMethodsStr(id);
957             } else {
958                 putEnabledInputMethodsStr(
959                         mEnabledInputMethodsStrCache + INPUT_METHOD_SEPARATOR + id);
960             }
961         }
962 
963         /**
964          * Build and put a string of EnabledInputMethods with removing specified Id.
965          * @return the specified id was removed or not.
966          */
buildAndPutEnabledInputMethodsStrRemovingIdLocked( StringBuilder builder, List<Pair<String, ArrayList<String>>> imsList, String id)967         boolean buildAndPutEnabledInputMethodsStrRemovingIdLocked(
968                 StringBuilder builder, List<Pair<String, ArrayList<String>>> imsList, String id) {
969             boolean isRemoved = false;
970             boolean needsAppendSeparator = false;
971             for (Pair<String, ArrayList<String>> ims: imsList) {
972                 String curId = ims.first;
973                 if (curId.equals(id)) {
974                     // We are disabling this input method, and it is
975                     // currently enabled.  Skip it to remove from the
976                     // new list.
977                     isRemoved = true;
978                 } else {
979                     if (needsAppendSeparator) {
980                         builder.append(INPUT_METHOD_SEPARATOR);
981                     } else {
982                         needsAppendSeparator = true;
983                     }
984                     buildEnabledInputMethodsSettingString(builder, ims);
985                 }
986             }
987             if (isRemoved) {
988                 // Update the setting with the new list of input methods.
989                 putEnabledInputMethodsStr(builder.toString());
990             }
991             return isRemoved;
992         }
993 
createEnabledInputMethodListLocked( List<Pair<String, ArrayList<String>>> imsList)994         private ArrayList<InputMethodInfo> createEnabledInputMethodListLocked(
995                 List<Pair<String, ArrayList<String>>> imsList) {
996             final ArrayList<InputMethodInfo> res = new ArrayList<>();
997             for (Pair<String, ArrayList<String>> ims: imsList) {
998                 InputMethodInfo info = mMethodMap.get(ims.first);
999                 if (info != null && !info.isVrOnly()) {
1000                     res.add(info);
1001                 }
1002             }
1003             return res;
1004         }
1005 
putEnabledInputMethodsStr(@ullable String str)1006         void putEnabledInputMethodsStr(@Nullable String str) {
1007             if (DEBUG) {
1008                 Slog.d(TAG, "putEnabledInputMethodStr: " + str);
1009             }
1010             if (TextUtils.isEmpty(str)) {
1011                 // OK to coalesce to null, since getEnabledInputMethodsStr() can take care of the
1012                 // empty data scenario.
1013                 putString(Settings.Secure.ENABLED_INPUT_METHODS, null);
1014             } else {
1015                 putString(Settings.Secure.ENABLED_INPUT_METHODS, str);
1016             }
1017             // TODO: Update callers of putEnabledInputMethodsStr to make str @NonNull.
1018             mEnabledInputMethodsStrCache = (str != null ? str : "");
1019         }
1020 
1021         @NonNull
getEnabledInputMethodsStr()1022         String getEnabledInputMethodsStr() {
1023             mEnabledInputMethodsStrCache = getString(Settings.Secure.ENABLED_INPUT_METHODS, "");
1024             if (DEBUG) {
1025                 Slog.d(TAG, "getEnabledInputMethodsStr: " + mEnabledInputMethodsStrCache
1026                         + ", " + mCurrentUserId);
1027             }
1028             return mEnabledInputMethodsStrCache;
1029         }
1030 
saveSubtypeHistory( List<Pair<String, String>> savedImes, String newImeId, String newSubtypeId)1031         private void saveSubtypeHistory(
1032                 List<Pair<String, String>> savedImes, String newImeId, String newSubtypeId) {
1033             StringBuilder builder = new StringBuilder();
1034             boolean isImeAdded = false;
1035             if (!TextUtils.isEmpty(newImeId) && !TextUtils.isEmpty(newSubtypeId)) {
1036                 builder.append(newImeId).append(INPUT_METHOD_SUBTYPE_SEPARATOR).append(
1037                         newSubtypeId);
1038                 isImeAdded = true;
1039             }
1040             for (Pair<String, String> ime: savedImes) {
1041                 String imeId = ime.first;
1042                 String subtypeId = ime.second;
1043                 if (TextUtils.isEmpty(subtypeId)) {
1044                     subtypeId = NOT_A_SUBTYPE_ID_STR;
1045                 }
1046                 if (isImeAdded) {
1047                     builder.append(INPUT_METHOD_SEPARATOR);
1048                 } else {
1049                     isImeAdded = true;
1050                 }
1051                 builder.append(imeId).append(INPUT_METHOD_SUBTYPE_SEPARATOR).append(
1052                         subtypeId);
1053             }
1054             // Remove the last INPUT_METHOD_SEPARATOR
1055             putSubtypeHistoryStr(builder.toString());
1056         }
1057 
addSubtypeToHistory(String imeId, String subtypeId)1058         private void addSubtypeToHistory(String imeId, String subtypeId) {
1059             List<Pair<String, String>> subtypeHistory = loadInputMethodAndSubtypeHistoryLocked();
1060             for (Pair<String, String> ime: subtypeHistory) {
1061                 if (ime.first.equals(imeId)) {
1062                     if (DEBUG) {
1063                         Slog.v(TAG, "Subtype found in the history: " + imeId + ", "
1064                                 + ime.second);
1065                     }
1066                     // We should break here
1067                     subtypeHistory.remove(ime);
1068                     break;
1069                 }
1070             }
1071             if (DEBUG) {
1072                 Slog.v(TAG, "Add subtype to the history: " + imeId + ", " + subtypeId);
1073             }
1074             saveSubtypeHistory(subtypeHistory, imeId, subtypeId);
1075         }
1076 
putSubtypeHistoryStr(@onNull String str)1077         private void putSubtypeHistoryStr(@NonNull String str) {
1078             if (DEBUG) {
1079                 Slog.d(TAG, "putSubtypeHistoryStr: " + str);
1080             }
1081             if (TextUtils.isEmpty(str)) {
1082                 // OK to coalesce to null, since getSubtypeHistoryStr() can take care of the empty
1083                 // data scenario.
1084                 putString(Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY, null);
1085             } else {
1086                 putString(Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY, str);
1087             }
1088         }
1089 
getLastInputMethodAndSubtypeLocked()1090         Pair<String, String> getLastInputMethodAndSubtypeLocked() {
1091             // Gets the first one from the history
1092             return getLastSubtypeForInputMethodLockedInternal(null);
1093         }
1094 
getLastSubtypeForInputMethodLocked(String imeId)1095         String getLastSubtypeForInputMethodLocked(String imeId) {
1096             Pair<String, String> ime = getLastSubtypeForInputMethodLockedInternal(imeId);
1097             if (ime != null) {
1098                 return ime.second;
1099             } else {
1100                 return null;
1101             }
1102         }
1103 
getLastSubtypeForInputMethodLockedInternal(String imeId)1104         private Pair<String, String> getLastSubtypeForInputMethodLockedInternal(String imeId) {
1105             List<Pair<String, ArrayList<String>>> enabledImes =
1106                     getEnabledInputMethodsAndSubtypeListLocked();
1107             List<Pair<String, String>> subtypeHistory = loadInputMethodAndSubtypeHistoryLocked();
1108             for (Pair<String, String> imeAndSubtype : subtypeHistory) {
1109                 final String imeInTheHistory = imeAndSubtype.first;
1110                 // If imeId is empty, returns the first IME and subtype in the history
1111                 if (TextUtils.isEmpty(imeId) || imeInTheHistory.equals(imeId)) {
1112                     final String subtypeInTheHistory = imeAndSubtype.second;
1113                     final String subtypeHashCode =
1114                             getEnabledSubtypeHashCodeForInputMethodAndSubtypeLocked(
1115                                     enabledImes, imeInTheHistory, subtypeInTheHistory);
1116                     if (!TextUtils.isEmpty(subtypeHashCode)) {
1117                         if (DEBUG) {
1118                             Slog.d(TAG, "Enabled subtype found in the history: " + subtypeHashCode);
1119                         }
1120                         return new Pair<>(imeInTheHistory, subtypeHashCode);
1121                     }
1122                 }
1123             }
1124             if (DEBUG) {
1125                 Slog.d(TAG, "No enabled IME found in the history");
1126             }
1127             return null;
1128         }
1129 
getEnabledSubtypeHashCodeForInputMethodAndSubtypeLocked(List<Pair<String, ArrayList<String>>> enabledImes, String imeId, String subtypeHashCode)1130         private String getEnabledSubtypeHashCodeForInputMethodAndSubtypeLocked(List<Pair<String,
1131                 ArrayList<String>>> enabledImes, String imeId, String subtypeHashCode) {
1132             for (Pair<String, ArrayList<String>> enabledIme: enabledImes) {
1133                 if (enabledIme.first.equals(imeId)) {
1134                     final ArrayList<String> explicitlyEnabledSubtypes = enabledIme.second;
1135                     final InputMethodInfo imi = mMethodMap.get(imeId);
1136                     if (explicitlyEnabledSubtypes.size() == 0) {
1137                         // If there are no explicitly enabled subtypes, applicable subtypes are
1138                         // enabled implicitly.
1139                         // If IME is enabled and no subtypes are enabled, applicable subtypes
1140                         // are enabled implicitly, so needs to treat them to be enabled.
1141                         if (imi != null && imi.getSubtypeCount() > 0) {
1142                             List<InputMethodSubtype> implicitlySelectedSubtypes =
1143                                     getImplicitlyApplicableSubtypesLocked(mRes, imi);
1144                             if (implicitlySelectedSubtypes != null) {
1145                                 final int N = implicitlySelectedSubtypes.size();
1146                                 for (int i = 0; i < N; ++i) {
1147                                     final InputMethodSubtype st = implicitlySelectedSubtypes.get(i);
1148                                     if (String.valueOf(st.hashCode()).equals(subtypeHashCode)) {
1149                                         return subtypeHashCode;
1150                                     }
1151                                 }
1152                             }
1153                         }
1154                     } else {
1155                         for (String s: explicitlyEnabledSubtypes) {
1156                             if (s.equals(subtypeHashCode)) {
1157                                 // If both imeId and subtypeId are enabled, return subtypeId.
1158                                 try {
1159                                     final int hashCode = Integer.parseInt(subtypeHashCode);
1160                                     // Check whether the subtype id is valid or not
1161                                     if (isValidSubtypeId(imi, hashCode)) {
1162                                         return s;
1163                                     } else {
1164                                         return NOT_A_SUBTYPE_ID_STR;
1165                                     }
1166                                 } catch (NumberFormatException e) {
1167                                     return NOT_A_SUBTYPE_ID_STR;
1168                                 }
1169                             }
1170                         }
1171                     }
1172                     // If imeId was enabled but subtypeId was disabled.
1173                     return NOT_A_SUBTYPE_ID_STR;
1174                 }
1175             }
1176             // If both imeId and subtypeId are disabled, return null
1177             return null;
1178         }
1179 
loadInputMethodAndSubtypeHistoryLocked()1180         private List<Pair<String, String>> loadInputMethodAndSubtypeHistoryLocked() {
1181             ArrayList<Pair<String, String>> imsList = new ArrayList<>();
1182             final String subtypeHistoryStr = getSubtypeHistoryStr();
1183             if (TextUtils.isEmpty(subtypeHistoryStr)) {
1184                 return imsList;
1185             }
1186             mInputMethodSplitter.setString(subtypeHistoryStr);
1187             while (mInputMethodSplitter.hasNext()) {
1188                 String nextImsStr = mInputMethodSplitter.next();
1189                 mSubtypeSplitter.setString(nextImsStr);
1190                 if (mSubtypeSplitter.hasNext()) {
1191                     String subtypeId = NOT_A_SUBTYPE_ID_STR;
1192                     // The first element is ime id.
1193                     String imeId = mSubtypeSplitter.next();
1194                     while (mSubtypeSplitter.hasNext()) {
1195                         subtypeId = mSubtypeSplitter.next();
1196                         break;
1197                     }
1198                     imsList.add(new Pair<>(imeId, subtypeId));
1199                 }
1200             }
1201             return imsList;
1202         }
1203 
1204         @NonNull
getSubtypeHistoryStr()1205         private String getSubtypeHistoryStr() {
1206             final String history = getString(Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY, "");
1207             if (DEBUG) {
1208                 Slog.d(TAG, "getSubtypeHistoryStr: " + history);
1209             }
1210             return history;
1211         }
1212 
putSelectedInputMethod(String imeId)1213         void putSelectedInputMethod(String imeId) {
1214             if (DEBUG) {
1215                 Slog.d(TAG, "putSelectedInputMethodStr: " + imeId + ", "
1216                         + mCurrentUserId);
1217             }
1218             putString(Settings.Secure.DEFAULT_INPUT_METHOD, imeId);
1219         }
1220 
putSelectedSubtype(int subtypeId)1221         void putSelectedSubtype(int subtypeId) {
1222             if (DEBUG) {
1223                 Slog.d(TAG, "putSelectedInputMethodSubtypeStr: " + subtypeId + ", "
1224                         + mCurrentUserId);
1225             }
1226             putInt(Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE, subtypeId);
1227         }
1228 
1229         @Nullable
getSelectedInputMethod()1230         String getSelectedInputMethod() {
1231             final String imi = getString(Settings.Secure.DEFAULT_INPUT_METHOD, null);
1232             if (DEBUG) {
1233                 Slog.d(TAG, "getSelectedInputMethodStr: " + imi);
1234             }
1235             return imi;
1236         }
1237 
isSubtypeSelected()1238         boolean isSubtypeSelected() {
1239             return getSelectedInputMethodSubtypeHashCode() != NOT_A_SUBTYPE_ID;
1240         }
1241 
getSelectedInputMethodSubtypeHashCode()1242         private int getSelectedInputMethodSubtypeHashCode() {
1243             return getInt(Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE, NOT_A_SUBTYPE_ID);
1244         }
1245 
isShowImeWithHardKeyboardEnabled()1246         boolean isShowImeWithHardKeyboardEnabled() {
1247             return getBoolean(Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD, false);
1248         }
1249 
setShowImeWithHardKeyboard(boolean show)1250         void setShowImeWithHardKeyboard(boolean show) {
1251             putBoolean(Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD, show);
1252         }
1253 
1254         @UserIdInt
getCurrentUserId()1255         public int getCurrentUserId() {
1256             return mCurrentUserId;
1257         }
1258 
getSelectedInputMethodSubtypeId(String selectedImiId)1259         int getSelectedInputMethodSubtypeId(String selectedImiId) {
1260             final InputMethodInfo imi = mMethodMap.get(selectedImiId);
1261             if (imi == null) {
1262                 return NOT_A_SUBTYPE_ID;
1263             }
1264             final int subtypeHashCode = getSelectedInputMethodSubtypeHashCode();
1265             return getSubtypeIdFromHashCode(imi, subtypeHashCode);
1266         }
1267 
saveCurrentInputMethodAndSubtypeToHistory(String curMethodId, InputMethodSubtype currentSubtype)1268         void saveCurrentInputMethodAndSubtypeToHistory(String curMethodId,
1269                 InputMethodSubtype currentSubtype) {
1270             String subtypeId = NOT_A_SUBTYPE_ID_STR;
1271             if (currentSubtype != null) {
1272                 subtypeId = String.valueOf(currentSubtype.hashCode());
1273             }
1274             if (canAddToLastInputMethod(currentSubtype)) {
1275                 addSubtypeToHistory(curMethodId, subtypeId);
1276             }
1277         }
1278 
dumpLocked(final Printer pw, final String prefix)1279         public void dumpLocked(final Printer pw, final String prefix) {
1280             pw.println(prefix + "mCurrentUserId=" + mCurrentUserId);
1281             pw.println(prefix + "mCurrentProfileIds=" + Arrays.toString(mCurrentProfileIds));
1282             pw.println(prefix + "mCopyOnWrite=" + mCopyOnWrite);
1283             pw.println(prefix + "mEnabledInputMethodsStrCache=" + mEnabledInputMethodsStrCache);
1284         }
1285     }
1286 
isSoftInputModeStateVisibleAllowed(int targetSdkVersion, @StartInputFlags int startInputFlags)1287     static boolean isSoftInputModeStateVisibleAllowed(int targetSdkVersion,
1288             @StartInputFlags int startInputFlags) {
1289         if (targetSdkVersion < Build.VERSION_CODES.P) {
1290             // for compatibility.
1291             return true;
1292         }
1293         if ((startInputFlags & StartInputFlags.VIEW_HAS_FOCUS) == 0) {
1294             return false;
1295         }
1296         if ((startInputFlags & StartInputFlags.IS_TEXT_EDITOR) == 0) {
1297             return false;
1298         }
1299         return true;
1300     }
1301 
1302     /**
1303      * Converts a user ID, which can be a pseudo user ID such as {@link UserHandle#USER_ALL} to a
1304      * list of real user IDs.
1305      *
1306      * <p>This method also converts profile user ID to profile parent user ID unless
1307      * {@link InputMethodSystemProperty#PER_PROFILE_IME_ENABLED} is {@code true}.</p>
1308      *
1309      * @param userIdToBeResolved A user ID. Two pseudo user ID {@link UserHandle#USER_CURRENT} and
1310      *                           {@link UserHandle#USER_ALL} are also supported
1311      * @param currentUserId A real user ID, which will be used when {@link UserHandle#USER_CURRENT}
1312      *                      is specified in {@code userIdToBeResolved}.
1313      * @param warningWriter A {@link PrintWriter} to output some debug messages. {@code null} if
1314      *                      no debug message is required.
1315      * @return An integer array that contain user IDs.
1316      */
resolveUserId(@serIdInt int userIdToBeResolved, @UserIdInt int currentUserId, @Nullable PrintWriter warningWriter)1317     static int[] resolveUserId(@UserIdInt int userIdToBeResolved,
1318             @UserIdInt int currentUserId, @Nullable PrintWriter warningWriter) {
1319         final UserManagerInternal userManagerInternal =
1320                 LocalServices.getService(UserManagerInternal.class);
1321 
1322         if (userIdToBeResolved == UserHandle.USER_ALL) {
1323             if (InputMethodSystemProperty.PER_PROFILE_IME_ENABLED) {
1324                 return userManagerInternal.getUserIds();
1325             }
1326             final IntArray result = new IntArray();
1327             for (int userId : userManagerInternal.getUserIds()) {
1328                 final int parentUserId = userManagerInternal.getProfileParentId(userId);
1329                 if (result.indexOf(parentUserId) < 0) {
1330                     result.add(parentUserId);
1331                 }
1332             }
1333             return result.toArray();
1334         }
1335 
1336         final int sourceUserId;
1337         if (userIdToBeResolved == UserHandle.USER_CURRENT) {
1338             sourceUserId = currentUserId;
1339         } else if (userIdToBeResolved < 0) {
1340             if (warningWriter != null) {
1341                 warningWriter.print("Pseudo user ID ");
1342                 warningWriter.print(userIdToBeResolved);
1343                 warningWriter.println(" is not supported.");
1344             }
1345             return new int[]{};
1346         } else if (userManagerInternal.exists(userIdToBeResolved)) {
1347             sourceUserId = userIdToBeResolved;
1348         } else {
1349             if (warningWriter != null) {
1350                 warningWriter.print("User #");
1351                 warningWriter.print(userIdToBeResolved);
1352                 warningWriter.println(" does not exit.");
1353             }
1354             return new int[]{};
1355         }
1356         final int resolvedUserId = InputMethodSystemProperty.PER_PROFILE_IME_ENABLED
1357                 ? sourceUserId : userManagerInternal.getProfileParentId(sourceUserId);
1358         return new int[]{resolvedUserId};
1359     }
1360 }
1361