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