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.internal.inputmethod;
18 
19 import static android.view.inputmethod.InputMethodManager.CONTROL_WINDOW_IS_TEXT_EDITOR;
20 import static android.view.inputmethod.InputMethodManager.CONTROL_WINDOW_VIEW_HAS_FOCUS;
21 
22 import android.annotation.NonNull;
23 import android.annotation.Nullable;
24 import android.annotation.UserIdInt;
25 import android.app.AppOpsManager;
26 import android.content.ContentResolver;
27 import android.content.Context;
28 import android.content.pm.ApplicationInfo;
29 import android.content.pm.IPackageManager;
30 import android.content.pm.PackageManager;
31 import android.content.res.Resources;
32 import android.os.Build;
33 import android.os.LocaleList;
34 import android.os.RemoteException;
35 import android.provider.Settings;
36 import android.text.TextUtils;
37 import android.text.TextUtils.SimpleStringSplitter;
38 import android.util.ArrayMap;
39 import android.util.ArraySet;
40 import android.util.Log;
41 import android.util.Pair;
42 import android.util.Printer;
43 import android.util.Slog;
44 import android.view.inputmethod.InputMethodInfo;
45 import android.view.inputmethod.InputMethodSubtype;
46 import android.view.textservice.SpellCheckerInfo;
47 import android.view.textservice.TextServicesManager;
48 
49 import com.android.internal.annotations.GuardedBy;
50 import com.android.internal.annotations.VisibleForTesting;
51 
52 import java.util.ArrayList;
53 import java.util.Arrays;
54 import java.util.HashMap;
55 import java.util.LinkedHashSet;
56 import java.util.List;
57 import java.util.Locale;
58 
59 /**
60  * InputMethodManagerUtils contains some static methods that provides IME informations.
61  * This methods are supposed to be used in both the framework and the Settings application.
62  */
63 public class InputMethodUtils {
64     public static final boolean DEBUG = false;
65     public static final int NOT_A_SUBTYPE_ID = -1;
66     public static final String SUBTYPE_MODE_ANY = null;
67     public static final String SUBTYPE_MODE_KEYBOARD = "keyboard";
68     public static final String SUBTYPE_MODE_VOICE = "voice";
69     private static final String TAG = "InputMethodUtils";
70     private static final Locale ENGLISH_LOCALE = new Locale("en");
71     private static final String NOT_A_SUBTYPE_ID_STR = String.valueOf(NOT_A_SUBTYPE_ID);
72     private static final String TAG_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE =
73             "EnabledWhenDefaultIsNotAsciiCapable";
74     private static final String TAG_ASCII_CAPABLE = "AsciiCapable";
75 
76     // The string for enabled input method is saved as follows:
77     // example: ("ime0;subtype0;subtype1;subtype2:ime1:ime2;subtype0")
78     private static final char INPUT_METHOD_SEPARATOR = ':';
79     private static final char INPUT_METHOD_SUBTYPE_SEPARATOR = ';';
80     /**
81      * Used in {@link #getFallbackLocaleForDefaultIme(ArrayList, Context)} to find the fallback IMEs
82      * that are mainly used until the system becomes ready. Note that {@link Locale} in this array
83      * is checked with {@link Locale#equals(Object)}, which means that {@code Locale.ENGLISH}
84      * doesn't automatically match {@code Locale("en", "IN")}.
85      */
86     private static final Locale[] SEARCH_ORDER_OF_FALLBACK_LOCALES = {
87         Locale.ENGLISH, // "en"
88         Locale.US, // "en_US"
89         Locale.UK, // "en_GB"
90     };
91 
92     // A temporary workaround for the performance concerns in
93     // #getImplicitlyApplicableSubtypesLocked(Resources, InputMethodInfo).
94     // TODO: Optimize all the critical paths including this one.
95     private static final Object sCacheLock = new Object();
96     @GuardedBy("sCacheLock")
97     private static LocaleList sCachedSystemLocales;
98     @GuardedBy("sCacheLock")
99     private static InputMethodInfo sCachedInputMethodInfo;
100     @GuardedBy("sCacheLock")
101     private static ArrayList<InputMethodSubtype> sCachedResult;
102 
InputMethodUtils()103     private InputMethodUtils() {
104         // This utility class is not publicly instantiable.
105     }
106 
107     // ----------------------------------------------------------------------
108     // Utilities for debug
getApiCallStack()109     public static String getApiCallStack() {
110         String apiCallStack = "";
111         try {
112             throw new RuntimeException();
113         } catch (RuntimeException e) {
114             final StackTraceElement[] frames = e.getStackTrace();
115             for (int j = 1; j < frames.length; ++j) {
116                 final String tempCallStack = frames[j].toString();
117                 if (TextUtils.isEmpty(apiCallStack)) {
118                     // Overwrite apiCallStack if it's empty
119                     apiCallStack = tempCallStack;
120                 } else if (tempCallStack.indexOf("Transact(") < 0) {
121                     // Overwrite apiCallStack if it's not a binder call
122                     apiCallStack = tempCallStack;
123                 } else {
124                     break;
125                 }
126             }
127         }
128         return apiCallStack;
129     }
130     // ----------------------------------------------------------------------
131 
isSystemIme(InputMethodInfo inputMethod)132     public static boolean isSystemIme(InputMethodInfo inputMethod) {
133         return (inputMethod.getServiceInfo().applicationInfo.flags
134                 & ApplicationInfo.FLAG_SYSTEM) != 0;
135     }
136 
isSystemImeThatHasSubtypeOf(final InputMethodInfo imi, final Context context, final boolean checkDefaultAttribute, @Nullable final Locale requiredLocale, final boolean checkCountry, final String requiredSubtypeMode)137     public static boolean isSystemImeThatHasSubtypeOf(final InputMethodInfo imi,
138             final Context context, final boolean checkDefaultAttribute,
139             @Nullable final Locale requiredLocale, final boolean checkCountry,
140             final String requiredSubtypeMode) {
141         if (!isSystemIme(imi)) {
142             return false;
143         }
144         if (checkDefaultAttribute && !imi.isDefault(context)) {
145             return false;
146         }
147         if (!containsSubtypeOf(imi, requiredLocale, checkCountry, requiredSubtypeMode)) {
148             return false;
149         }
150         return true;
151     }
152 
153     @Nullable
getFallbackLocaleForDefaultIme(final ArrayList<InputMethodInfo> imis, final Context context)154     public static Locale getFallbackLocaleForDefaultIme(final ArrayList<InputMethodInfo> imis,
155             final Context context) {
156         // At first, find the fallback locale from the IMEs that are declared as "default" in the
157         // current locale.  Note that IME developers can declare an IME as "default" only for
158         // some particular locales but "not default" for other locales.
159         for (final Locale fallbackLocale : SEARCH_ORDER_OF_FALLBACK_LOCALES) {
160             for (int i = 0; i < imis.size(); ++i) {
161                 if (isSystemImeThatHasSubtypeOf(imis.get(i), context,
162                         true /* checkDefaultAttribute */, fallbackLocale,
163                         true /* checkCountry */, SUBTYPE_MODE_KEYBOARD)) {
164                     return fallbackLocale;
165                 }
166             }
167         }
168         // If no fallback locale is found in the above condition, find fallback locales regardless
169         // of the "default" attribute as a last resort.
170         for (final Locale fallbackLocale : SEARCH_ORDER_OF_FALLBACK_LOCALES) {
171             for (int i = 0; i < imis.size(); ++i) {
172                 if (isSystemImeThatHasSubtypeOf(imis.get(i), context,
173                         false /* checkDefaultAttribute */, fallbackLocale,
174                         true /* checkCountry */, SUBTYPE_MODE_KEYBOARD)) {
175                     return fallbackLocale;
176                 }
177             }
178         }
179         Slog.w(TAG, "Found no fallback locale. imis=" + Arrays.toString(imis.toArray()));
180         return null;
181     }
182 
isSystemAuxilialyImeThatHasAutomaticSubtype(final InputMethodInfo imi, final Context context, final boolean checkDefaultAttribute)183     private static boolean isSystemAuxilialyImeThatHasAutomaticSubtype(final InputMethodInfo imi,
184             final Context context, final boolean checkDefaultAttribute) {
185         if (!isSystemIme(imi)) {
186             return false;
187         }
188         if (checkDefaultAttribute && !imi.isDefault(context)) {
189             return false;
190         }
191         if (!imi.isAuxiliaryIme()) {
192             return false;
193         }
194         final int subtypeCount = imi.getSubtypeCount();
195         for (int i = 0; i < subtypeCount; ++i) {
196             final InputMethodSubtype s = imi.getSubtypeAt(i);
197             if (s.overridesImplicitlyEnabledSubtype()) {
198                 return true;
199             }
200         }
201         return false;
202     }
203 
getSystemLocaleFromContext(final Context context)204     public static Locale getSystemLocaleFromContext(final Context context) {
205         try {
206             return context.getResources().getConfiguration().locale;
207         } catch (Resources.NotFoundException ex) {
208             return null;
209         }
210     }
211 
212     private static final class InputMethodListBuilder {
213         // Note: We use LinkedHashSet instead of android.util.ArraySet because the enumeration
214         // order can have non-trivial effect in the call sites.
215         @NonNull
216         private final LinkedHashSet<InputMethodInfo> mInputMethodSet = new LinkedHashSet<>();
217 
fillImes(final ArrayList<InputMethodInfo> imis, final Context context, final boolean checkDefaultAttribute, @Nullable final Locale locale, final boolean checkCountry, final String requiredSubtypeMode)218         public InputMethodListBuilder fillImes(final ArrayList<InputMethodInfo> imis,
219                 final Context context, final boolean checkDefaultAttribute,
220                 @Nullable final Locale locale, final boolean checkCountry,
221                 final String requiredSubtypeMode) {
222             for (int i = 0; i < imis.size(); ++i) {
223                 final InputMethodInfo imi = imis.get(i);
224                 if (isSystemImeThatHasSubtypeOf(imi, context, checkDefaultAttribute, locale,
225                         checkCountry, requiredSubtypeMode)) {
226                     mInputMethodSet.add(imi);
227                 }
228             }
229             return this;
230         }
231 
232         // TODO: The behavior of InputMethodSubtype#overridesImplicitlyEnabledSubtype() should be
233         // documented more clearly.
fillAuxiliaryImes(final ArrayList<InputMethodInfo> imis, final Context context)234         public InputMethodListBuilder fillAuxiliaryImes(final ArrayList<InputMethodInfo> imis,
235                 final Context context) {
236             // If one or more auxiliary input methods are available, OK to stop populating the list.
237             for (final InputMethodInfo imi : mInputMethodSet) {
238                 if (imi.isAuxiliaryIme()) {
239                     return this;
240                 }
241             }
242             boolean added = false;
243             for (int i = 0; i < imis.size(); ++i) {
244                 final InputMethodInfo imi = imis.get(i);
245                 if (isSystemAuxilialyImeThatHasAutomaticSubtype(imi, context,
246                         true /* checkDefaultAttribute */)) {
247                     mInputMethodSet.add(imi);
248                     added = true;
249                 }
250             }
251             if (added) {
252                 return this;
253             }
254             for (int i = 0; i < imis.size(); ++i) {
255                 final InputMethodInfo imi = imis.get(i);
256                 if (isSystemAuxilialyImeThatHasAutomaticSubtype(imi, context,
257                         false /* checkDefaultAttribute */)) {
258                     mInputMethodSet.add(imi);
259                 }
260             }
261             return this;
262         }
263 
isEmpty()264         public boolean isEmpty() {
265             return mInputMethodSet.isEmpty();
266         }
267 
268         @NonNull
build()269         public ArrayList<InputMethodInfo> build() {
270             return new ArrayList<>(mInputMethodSet);
271         }
272     }
273 
getMinimumKeyboardSetWithSystemLocale( final ArrayList<InputMethodInfo> imis, final Context context, @Nullable final Locale systemLocale, @Nullable final Locale fallbackLocale)274     private static InputMethodListBuilder getMinimumKeyboardSetWithSystemLocale(
275             final ArrayList<InputMethodInfo> imis, final Context context,
276             @Nullable final Locale systemLocale, @Nullable final Locale fallbackLocale) {
277         // Once the system becomes ready, we pick up at least one keyboard in the following order.
278         // Secondary users fall into this category in general.
279         // 1. checkDefaultAttribute: true, locale: systemLocale, checkCountry: true
280         // 2. checkDefaultAttribute: true, locale: systemLocale, checkCountry: false
281         // 3. checkDefaultAttribute: true, locale: fallbackLocale, checkCountry: true
282         // 4. checkDefaultAttribute: true, locale: fallbackLocale, checkCountry: false
283         // 5. checkDefaultAttribute: false, locale: fallbackLocale, checkCountry: true
284         // 6. checkDefaultAttribute: false, locale: fallbackLocale, checkCountry: false
285         // TODO: We should check isAsciiCapable instead of relying on fallbackLocale.
286 
287         final InputMethodListBuilder builder = new InputMethodListBuilder();
288         builder.fillImes(imis, context, true /* checkDefaultAttribute */, systemLocale,
289                 true /* checkCountry */, SUBTYPE_MODE_KEYBOARD);
290         if (!builder.isEmpty()) {
291             return builder;
292         }
293         builder.fillImes(imis, context, true /* checkDefaultAttribute */, systemLocale,
294                 false /* checkCountry */, SUBTYPE_MODE_KEYBOARD);
295         if (!builder.isEmpty()) {
296             return builder;
297         }
298         builder.fillImes(imis, context, true /* checkDefaultAttribute */, fallbackLocale,
299                 true /* checkCountry */, SUBTYPE_MODE_KEYBOARD);
300         if (!builder.isEmpty()) {
301             return builder;
302         }
303         builder.fillImes(imis, context, true /* checkDefaultAttribute */, fallbackLocale,
304                 false /* checkCountry */, SUBTYPE_MODE_KEYBOARD);
305         if (!builder.isEmpty()) {
306             return builder;
307         }
308         builder.fillImes(imis, context, false /* checkDefaultAttribute */, fallbackLocale,
309                 true /* checkCountry */, SUBTYPE_MODE_KEYBOARD);
310         if (!builder.isEmpty()) {
311             return builder;
312         }
313         builder.fillImes(imis, context, false /* checkDefaultAttribute */, fallbackLocale,
314                 false /* checkCountry */, SUBTYPE_MODE_KEYBOARD);
315         if (!builder.isEmpty()) {
316             return builder;
317         }
318         Slog.w(TAG, "No software keyboard is found. imis=" + Arrays.toString(imis.toArray())
319                 + " systemLocale=" + systemLocale + " fallbackLocale=" + fallbackLocale);
320         return builder;
321     }
322 
getDefaultEnabledImes( Context context, ArrayList<InputMethodInfo> imis, boolean onlyMinimum)323     public static ArrayList<InputMethodInfo> getDefaultEnabledImes(
324             Context context, ArrayList<InputMethodInfo> imis, boolean onlyMinimum) {
325         final Locale fallbackLocale = getFallbackLocaleForDefaultIme(imis, context);
326         // We will primarily rely on the system locale, but also keep relying on the fallback locale
327         // as a last resort.
328         // Also pick up suitable IMEs regardless of the software keyboard support (e.g. Voice IMEs),
329         // then pick up suitable auxiliary IMEs when necessary (e.g. Voice IMEs with "automatic"
330         // subtype)
331         final Locale systemLocale = getSystemLocaleFromContext(context);
332         final InputMethodListBuilder builder =
333                 getMinimumKeyboardSetWithSystemLocale(imis, context, systemLocale, fallbackLocale);
334         if (!onlyMinimum) {
335             builder.fillImes(imis, context, true /* checkDefaultAttribute */, systemLocale,
336                     true /* checkCountry */, SUBTYPE_MODE_ANY)
337                     .fillAuxiliaryImes(imis, context);
338         }
339         return builder.build();
340     }
341 
getDefaultEnabledImes( Context context, ArrayList<InputMethodInfo> imis)342     public static ArrayList<InputMethodInfo> getDefaultEnabledImes(
343             Context context, ArrayList<InputMethodInfo> imis) {
344         return getDefaultEnabledImes(context, imis, false /* onlyMinimum */);
345     }
346 
constructLocaleFromString(String localeStr)347     public static Locale constructLocaleFromString(String localeStr) {
348         if (TextUtils.isEmpty(localeStr)) {
349             return null;
350         }
351         // TODO: Use {@link Locale#toLanguageTag()} and {@link Locale#forLanguageTag(languageTag)}.
352         String[] localeParams = localeStr.split("_", 3);
353         if (localeParams.length >= 1 && "tl".equals(localeParams[0])) {
354              // Convert a locale whose language is "tl" to one whose language is "fil".
355              // For example, "tl_PH" will get converted to "fil_PH".
356              // Versions of Android earlier than Lollipop did not support three letter language
357              // codes, and used "tl" (Tagalog) as the language string for "fil" (Filipino).
358              // On Lollipop and above, the current three letter version must be used.
359              localeParams[0] = "fil";
360         }
361         // The length of localeStr is guaranteed to always return a 1 <= value <= 3
362         // because localeStr is not empty.
363         if (localeParams.length == 1) {
364             return new Locale(localeParams[0]);
365         } else if (localeParams.length == 2) {
366             return new Locale(localeParams[0], localeParams[1]);
367         } else if (localeParams.length == 3) {
368             return new Locale(localeParams[0], localeParams[1], localeParams[2]);
369         }
370         return null;
371     }
372 
containsSubtypeOf(final InputMethodInfo imi, @Nullable final Locale locale, final boolean checkCountry, final String mode)373     public static boolean containsSubtypeOf(final InputMethodInfo imi,
374             @Nullable final Locale locale, final boolean checkCountry, final String mode) {
375         if (locale == null) {
376             return false;
377         }
378         final int N = imi.getSubtypeCount();
379         for (int i = 0; i < N; ++i) {
380             final InputMethodSubtype subtype = imi.getSubtypeAt(i);
381             if (checkCountry) {
382                 final Locale subtypeLocale = subtype.getLocaleObject();
383                 if (subtypeLocale == null ||
384                         !TextUtils.equals(subtypeLocale.getLanguage(), locale.getLanguage()) ||
385                         !TextUtils.equals(subtypeLocale.getCountry(), locale.getCountry())) {
386                     continue;
387                 }
388             } else {
389                 final Locale subtypeLocale = new Locale(getLanguageFromLocaleString(
390                         subtype.getLocale()));
391                 if (!TextUtils.equals(subtypeLocale.getLanguage(), locale.getLanguage())) {
392                     continue;
393                 }
394             }
395             if (mode == SUBTYPE_MODE_ANY || TextUtils.isEmpty(mode) ||
396                     mode.equalsIgnoreCase(subtype.getMode())) {
397                 return true;
398             }
399         }
400         return false;
401     }
402 
getSubtypes(InputMethodInfo imi)403     public static ArrayList<InputMethodSubtype> getSubtypes(InputMethodInfo imi) {
404         ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
405         final int subtypeCount = imi.getSubtypeCount();
406         for (int i = 0; i < subtypeCount; ++i) {
407             subtypes.add(imi.getSubtypeAt(i));
408         }
409         return subtypes;
410     }
411 
getOverridingImplicitlyEnabledSubtypes( InputMethodInfo imi, String mode)412     public static ArrayList<InputMethodSubtype> getOverridingImplicitlyEnabledSubtypes(
413             InputMethodInfo imi, String mode) {
414         ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
415         final int subtypeCount = imi.getSubtypeCount();
416         for (int i = 0; i < subtypeCount; ++i) {
417             final InputMethodSubtype subtype = imi.getSubtypeAt(i);
418             if (subtype.overridesImplicitlyEnabledSubtype() && subtype.getMode().equals(mode)) {
419                 subtypes.add(subtype);
420             }
421         }
422         return subtypes;
423     }
424 
getMostApplicableDefaultIME(List<InputMethodInfo> enabledImes)425     public static InputMethodInfo getMostApplicableDefaultIME(List<InputMethodInfo> enabledImes) {
426         if (enabledImes == null || enabledImes.isEmpty()) {
427             return null;
428         }
429         // We'd prefer to fall back on a system IME, since that is safer.
430         int i = enabledImes.size();
431         int firstFoundSystemIme = -1;
432         while (i > 0) {
433             i--;
434             final InputMethodInfo imi = enabledImes.get(i);
435             if (imi.isAuxiliaryIme()) {
436                 continue;
437             }
438             if (InputMethodUtils.isSystemIme(imi)
439                     && containsSubtypeOf(imi, ENGLISH_LOCALE, false /* checkCountry */,
440                             SUBTYPE_MODE_KEYBOARD)) {
441                 return imi;
442             }
443             if (firstFoundSystemIme < 0 && InputMethodUtils.isSystemIme(imi)) {
444                 firstFoundSystemIme = i;
445             }
446         }
447         return enabledImes.get(Math.max(firstFoundSystemIme, 0));
448     }
449 
isValidSubtypeId(InputMethodInfo imi, int subtypeHashCode)450     public static boolean isValidSubtypeId(InputMethodInfo imi, int subtypeHashCode) {
451         return getSubtypeIdFromHashCode(imi, subtypeHashCode) != NOT_A_SUBTYPE_ID;
452     }
453 
getSubtypeIdFromHashCode(InputMethodInfo imi, int subtypeHashCode)454     public 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     public 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 HashMap<String, InputMethodSubtype> applicableModeAndSubtypesMap = new HashMap<>();
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 HashMap<String, ArrayList<InputMethodSubtype>> nonKeyboardSubtypesMap =
527                 new HashMap<>();
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.containsExtraValueKey(TAG_ASCII_CAPABLE)) {
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     public 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     public 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     public static boolean canAddToLastInputMethod(InputMethodSubtype subtype) {
662         if (subtype == null) return true;
663         return !subtype.isAuxiliary();
664     }
665 
setNonSelectedSystemImesDisabledUntilUsed( IPackageManager packageManager, List<InputMethodInfo> enabledImis, int userId, String callingPackage)666     public static void setNonSelectedSystemImesDisabledUntilUsed(
667             IPackageManager packageManager, List<InputMethodInfo> enabledImis,
668             int userId, String callingPackage) {
669         if (DEBUG) {
670             Slog.d(TAG, "setNonSelectedSystemImesDisabledUntilUsed");
671         }
672         final String[] systemImesDisabledUntilUsed = Resources.getSystem().getStringArray(
673                 com.android.internal.R.array.config_disabledUntilUsedPreinstalledImes);
674         if (systemImesDisabledUntilUsed == null || systemImesDisabledUntilUsed.length == 0) {
675             return;
676         }
677         // Only the current spell checker should be treated as an enabled one.
678         final SpellCheckerInfo currentSpellChecker =
679                 TextServicesManager.getInstance().getCurrentSpellChecker();
680         for (final String packageName : systemImesDisabledUntilUsed) {
681             if (DEBUG) {
682                 Slog.d(TAG, "check " + packageName);
683             }
684             boolean enabledIme = false;
685             for (int j = 0; j < enabledImis.size(); ++j) {
686                 final InputMethodInfo imi = enabledImis.get(j);
687                 if (packageName.equals(imi.getPackageName())) {
688                     enabledIme = true;
689                     break;
690                 }
691             }
692             if (enabledIme) {
693                 // enabled ime. skip
694                 continue;
695             }
696             if (currentSpellChecker != null
697                     && packageName.equals(currentSpellChecker.getPackageName())) {
698                 // enabled spell checker. skip
699                 if (DEBUG) {
700                     Slog.d(TAG, packageName + " is the current spell checker. skip");
701                 }
702                 continue;
703             }
704             ApplicationInfo ai = null;
705             try {
706                 ai = packageManager.getApplicationInfo(packageName,
707                         PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS, userId);
708             } catch (RemoteException e) {
709                 Slog.w(TAG, "getApplicationInfo failed. packageName=" + packageName
710                         + " userId=" + userId, e);
711                 continue;
712             }
713             if (ai == null) {
714                 // No app found for packageName
715                 continue;
716             }
717             final boolean isSystemPackage = (ai.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
718             if (!isSystemPackage) {
719                 continue;
720             }
721             setDisabledUntilUsed(packageManager, packageName, userId, callingPackage);
722         }
723     }
724 
setDisabledUntilUsed(IPackageManager packageManager, String packageName, int userId, String callingPackage)725     private static void setDisabledUntilUsed(IPackageManager packageManager, String packageName,
726             int userId, String callingPackage) {
727         final int state;
728         try {
729             state = packageManager.getApplicationEnabledSetting(packageName, userId);
730         } catch (RemoteException e) {
731             Slog.w(TAG, "getApplicationEnabledSetting failed. packageName=" + packageName
732                     + " userId=" + userId, e);
733             return;
734         }
735         if (state == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
736                 || state == PackageManager.COMPONENT_ENABLED_STATE_ENABLED) {
737             if (DEBUG) {
738                 Slog.d(TAG, "Update state(" + packageName + "): DISABLED_UNTIL_USED");
739             }
740             try {
741                 packageManager.setApplicationEnabledSetting(packageName,
742                         PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED,
743                         0 /* newState */, userId, callingPackage);
744             } catch (RemoteException e) {
745                 Slog.w(TAG, "setApplicationEnabledSetting failed. packageName=" + packageName
746                         + " userId=" + userId + " callingPackage=" + callingPackage, e);
747                 return;
748             }
749         } else {
750             if (DEBUG) {
751                 Slog.d(TAG, packageName + " is already DISABLED_UNTIL_USED");
752             }
753         }
754     }
755 
getImeAndSubtypeDisplayName(Context context, InputMethodInfo imi, InputMethodSubtype subtype)756     public static CharSequence getImeAndSubtypeDisplayName(Context context, InputMethodInfo imi,
757             InputMethodSubtype subtype) {
758         final CharSequence imiLabel = imi.loadLabel(context.getPackageManager());
759         return subtype != null
760                 ? TextUtils.concat(subtype.getDisplayName(context,
761                         imi.getPackageName(), imi.getServiceInfo().applicationInfo),
762                                 (TextUtils.isEmpty(imiLabel) ?
763                                         "" : " - " + imiLabel))
764                 : imiLabel;
765     }
766 
767     /**
768      * Returns true if a package name belongs to a UID.
769      *
770      * <p>This is a simple wrapper of {@link AppOpsManager#checkPackage(int, String)}.</p>
771      * @param appOpsManager the {@link AppOpsManager} object to be used for the validation.
772      * @param uid the UID to be validated.
773      * @param packageName the package name.
774      * @return {@code true} if the package name belongs to the UID.
775      */
checkIfPackageBelongsToUid(final AppOpsManager appOpsManager, final int uid, final String packageName)776     public static boolean checkIfPackageBelongsToUid(final AppOpsManager appOpsManager,
777             final int uid, final String packageName) {
778         try {
779             appOpsManager.checkPackage(uid, packageName);
780             return true;
781         } catch (SecurityException e) {
782             return false;
783         }
784     }
785 
786     /**
787      * Parses the setting stored input methods and subtypes string value.
788      *
789      * @param inputMethodsAndSubtypesString The input method subtypes value stored in settings.
790      * @return Map from input method ID to set of input method subtypes IDs.
791      */
792     @VisibleForTesting
parseInputMethodsAndSubtypesString( @ullable final String inputMethodsAndSubtypesString)793     public static ArrayMap<String, ArraySet<String>> parseInputMethodsAndSubtypesString(
794             @Nullable final String inputMethodsAndSubtypesString) {
795 
796         final ArrayMap<String, ArraySet<String>> imeMap = new ArrayMap<>();
797         if (TextUtils.isEmpty(inputMethodsAndSubtypesString)) {
798             return imeMap;
799         }
800 
801         final SimpleStringSplitter typeSplitter =
802                 new SimpleStringSplitter(INPUT_METHOD_SEPARATOR);
803         final SimpleStringSplitter subtypeSplitter =
804                 new SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATOR);
805 
806         List<Pair<String, ArrayList<String>>> allImeSettings =
807                 InputMethodSettings.buildInputMethodsAndSubtypeList(inputMethodsAndSubtypesString,
808                         typeSplitter,
809                         subtypeSplitter);
810         for (Pair<String, ArrayList<String>> ime : allImeSettings) {
811             ArraySet<String> subtypes = new ArraySet<>();
812             if (ime.second != null) {
813                 subtypes.addAll(ime.second);
814             }
815             imeMap.put(ime.first, subtypes);
816         }
817         return imeMap;
818     }
819 
820     @NonNull
buildInputMethodsAndSubtypesString( @onNull final ArrayMap<String, ArraySet<String>> map)821     public static String buildInputMethodsAndSubtypesString(
822             @NonNull final ArrayMap<String, ArraySet<String>> map) {
823         // we want to use the canonical InputMethodSettings implementation,
824         // so we convert data structures first.
825         List<Pair<String, ArrayList<String>>> imeMap = new ArrayList<>(4);
826         for (ArrayMap.Entry<String, ArraySet<String>> entry : map.entrySet()) {
827             final String imeName = entry.getKey();
828             final ArraySet<String> subtypeSet = entry.getValue();
829             final ArrayList<String> subtypes = new ArrayList<>(2);
830             if (subtypeSet != null) {
831                 subtypes.addAll(subtypeSet);
832             }
833             imeMap.add(new Pair<>(imeName, subtypes));
834         }
835         return InputMethodSettings.buildInputMethodsSettingString(imeMap);
836     }
837 
838     /**
839      * Utility class for putting and getting settings for InputMethod
840      * TODO: Move all putters and getters of settings to this class.
841      */
842     public static class InputMethodSettings {
843         private final TextUtils.SimpleStringSplitter mInputMethodSplitter =
844                 new TextUtils.SimpleStringSplitter(INPUT_METHOD_SEPARATOR);
845 
846         private final TextUtils.SimpleStringSplitter mSubtypeSplitter =
847                 new TextUtils.SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATOR);
848 
849         private final Resources mRes;
850         private final ContentResolver mResolver;
851         private final HashMap<String, InputMethodInfo> mMethodMap;
852 
853         /**
854          * On-memory data store to emulate when {@link #mCopyOnWrite} is {@code true}.
855          */
856         private final HashMap<String, String> mCopyOnWriteDataStore = new HashMap<>();
857 
858         private boolean mCopyOnWrite = false;
859         @NonNull
860         private String mEnabledInputMethodsStrCache = "";
861         @UserIdInt
862         private int mCurrentUserId;
863         private int[] mCurrentProfileIds = new int[0];
864 
buildEnabledInputMethodsSettingString( StringBuilder builder, Pair<String, ArrayList<String>> ime)865         private static void buildEnabledInputMethodsSettingString(
866                 StringBuilder builder, Pair<String, ArrayList<String>> ime) {
867             builder.append(ime.first);
868             // Inputmethod and subtypes are saved in the settings as follows:
869             // ime0;subtype0;subtype1:ime1;subtype0:ime2:ime3;subtype0;subtype1
870             for (String subtypeId: ime.second) {
871                 builder.append(INPUT_METHOD_SUBTYPE_SEPARATOR).append(subtypeId);
872             }
873         }
874 
buildInputMethodsSettingString( List<Pair<String, ArrayList<String>>> allImeSettingsMap)875         public static String buildInputMethodsSettingString(
876                 List<Pair<String, ArrayList<String>>> allImeSettingsMap) {
877             final StringBuilder b = new StringBuilder();
878             boolean needsSeparator = false;
879             for (Pair<String, ArrayList<String>> ime : allImeSettingsMap) {
880                 if (needsSeparator) {
881                     b.append(INPUT_METHOD_SEPARATOR);
882                 }
883                 buildEnabledInputMethodsSettingString(b, ime);
884                 needsSeparator = true;
885             }
886             return b.toString();
887         }
888 
buildInputMethodsAndSubtypeList( String enabledInputMethodsStr, TextUtils.SimpleStringSplitter inputMethodSplitter, TextUtils.SimpleStringSplitter subtypeSplitter)889         public static List<Pair<String, ArrayList<String>>> buildInputMethodsAndSubtypeList(
890                 String enabledInputMethodsStr,
891                 TextUtils.SimpleStringSplitter inputMethodSplitter,
892                 TextUtils.SimpleStringSplitter subtypeSplitter) {
893             ArrayList<Pair<String, ArrayList<String>>> imsList = new ArrayList<>();
894             if (TextUtils.isEmpty(enabledInputMethodsStr)) {
895                 return imsList;
896             }
897             inputMethodSplitter.setString(enabledInputMethodsStr);
898             while (inputMethodSplitter.hasNext()) {
899                 String nextImsStr = inputMethodSplitter.next();
900                 subtypeSplitter.setString(nextImsStr);
901                 if (subtypeSplitter.hasNext()) {
902                     ArrayList<String> subtypeHashes = new ArrayList<>();
903                     // The first element is ime id.
904                     String imeId = subtypeSplitter.next();
905                     while (subtypeSplitter.hasNext()) {
906                         subtypeHashes.add(subtypeSplitter.next());
907                     }
908                     imsList.add(new Pair<>(imeId, subtypeHashes));
909                 }
910             }
911             return imsList;
912         }
913 
InputMethodSettings( Resources res, ContentResolver resolver, HashMap<String, InputMethodInfo> methodMap, ArrayList<InputMethodInfo> methodList, @UserIdInt int userId, boolean copyOnWrite)914         public InputMethodSettings(
915                 Resources res, ContentResolver resolver,
916                 HashMap<String, InputMethodInfo> methodMap, ArrayList<InputMethodInfo> methodList,
917                 @UserIdInt int userId, boolean copyOnWrite) {
918             mRes = res;
919             mResolver = resolver;
920             mMethodMap = methodMap;
921             switchCurrentUser(userId, copyOnWrite);
922         }
923 
924         /**
925          * Must be called when the current user is changed.
926          *
927          * @param userId The user ID.
928          * @param copyOnWrite If {@code true}, for each settings key
929          * (e.g. {@link Settings.Secure#ACTION_INPUT_METHOD_SUBTYPE_SETTINGS}) we use the actual
930          * settings on the {@link Settings.Secure} until we do the first write operation.
931          */
switchCurrentUser(@serIdInt int userId, boolean copyOnWrite)932         public void switchCurrentUser(@UserIdInt int userId, boolean copyOnWrite) {
933             if (DEBUG) {
934                 Slog.d(TAG, "--- Switch the current user from " + mCurrentUserId + " to " + userId);
935             }
936             if (mCurrentUserId != userId || mCopyOnWrite != copyOnWrite) {
937                 mCopyOnWriteDataStore.clear();
938                 mEnabledInputMethodsStrCache = "";
939                 // TODO: mCurrentProfileIds should be cleared here.
940             }
941             mCurrentUserId = userId;
942             mCopyOnWrite = copyOnWrite;
943             // TODO: mCurrentProfileIds should be updated here.
944         }
945 
putString(@onNull final String key, @Nullable final String str)946         private void putString(@NonNull final String key, @Nullable final String str) {
947             if (mCopyOnWrite) {
948                 mCopyOnWriteDataStore.put(key, str);
949             } else {
950                 Settings.Secure.putStringForUser(mResolver, key, str, mCurrentUserId);
951             }
952         }
953 
954         @Nullable
getString(@onNull final String key, @Nullable final String defaultValue)955         private String getString(@NonNull final String key, @Nullable final String defaultValue) {
956             final String result;
957             if (mCopyOnWrite && mCopyOnWriteDataStore.containsKey(key)) {
958                 result = mCopyOnWriteDataStore.get(key);
959             } else {
960                 result = Settings.Secure.getStringForUser(mResolver, key, mCurrentUserId);
961             }
962             return result != null ? result : defaultValue;
963         }
964 
putInt(final String key, final int value)965         private void putInt(final String key, final int value) {
966             if (mCopyOnWrite) {
967                 mCopyOnWriteDataStore.put(key, String.valueOf(value));
968             } else {
969                 Settings.Secure.putIntForUser(mResolver, key, value, mCurrentUserId);
970             }
971         }
972 
getInt(final String key, final int defaultValue)973         private int getInt(final String key, final int defaultValue) {
974             if (mCopyOnWrite && mCopyOnWriteDataStore.containsKey(key)) {
975                 final String result = mCopyOnWriteDataStore.get(key);
976                 return result != null ? Integer.parseInt(result) : 0;
977             }
978             return Settings.Secure.getIntForUser(mResolver, key, defaultValue, mCurrentUserId);
979         }
980 
putBoolean(final String key, final boolean value)981         private void putBoolean(final String key, final boolean value) {
982             putInt(key, value ? 1 : 0);
983         }
984 
getBoolean(final String key, final boolean defaultValue)985         private boolean getBoolean(final String key, final boolean defaultValue) {
986             return getInt(key, defaultValue ? 1 : 0) == 1;
987         }
988 
setCurrentProfileIds(int[] currentProfileIds)989         public void setCurrentProfileIds(int[] currentProfileIds) {
990             synchronized (this) {
991                 mCurrentProfileIds = currentProfileIds;
992             }
993         }
994 
isCurrentProfile(int userId)995         public boolean isCurrentProfile(int userId) {
996             synchronized (this) {
997                 if (userId == mCurrentUserId) return true;
998                 for (int i = 0; i < mCurrentProfileIds.length; i++) {
999                     if (userId == mCurrentProfileIds[i]) return true;
1000                 }
1001                 return false;
1002             }
1003         }
1004 
getEnabledInputMethodListLocked()1005         public ArrayList<InputMethodInfo> getEnabledInputMethodListLocked() {
1006             return createEnabledInputMethodListLocked(
1007                     getEnabledInputMethodsAndSubtypeListLocked());
1008         }
1009 
getEnabledInputMethodSubtypeListLocked( Context context, InputMethodInfo imi, boolean allowsImplicitlySelectedSubtypes)1010         public List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked(
1011                 Context context, InputMethodInfo imi, boolean allowsImplicitlySelectedSubtypes) {
1012             List<InputMethodSubtype> enabledSubtypes =
1013                     getEnabledInputMethodSubtypeListLocked(imi);
1014             if (allowsImplicitlySelectedSubtypes && enabledSubtypes.isEmpty()) {
1015                 enabledSubtypes = InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
1016                         context.getResources(), imi);
1017             }
1018             return InputMethodSubtype.sort(context, 0, imi, enabledSubtypes);
1019         }
1020 
getEnabledInputMethodSubtypeListLocked( InputMethodInfo imi)1021         public List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked(
1022                 InputMethodInfo imi) {
1023             List<Pair<String, ArrayList<String>>> imsList =
1024                     getEnabledInputMethodsAndSubtypeListLocked();
1025             ArrayList<InputMethodSubtype> enabledSubtypes = new ArrayList<>();
1026             if (imi != null) {
1027                 for (Pair<String, ArrayList<String>> imsPair : imsList) {
1028                     InputMethodInfo info = mMethodMap.get(imsPair.first);
1029                     if (info != null && info.getId().equals(imi.getId())) {
1030                         final int subtypeCount = info.getSubtypeCount();
1031                         for (int i = 0; i < subtypeCount; ++i) {
1032                             InputMethodSubtype ims = info.getSubtypeAt(i);
1033                             for (String s: imsPair.second) {
1034                                 if (String.valueOf(ims.hashCode()).equals(s)) {
1035                                     enabledSubtypes.add(ims);
1036                                 }
1037                             }
1038                         }
1039                         break;
1040                     }
1041                 }
1042             }
1043             return enabledSubtypes;
1044         }
1045 
getEnabledInputMethodsAndSubtypeListLocked()1046         public List<Pair<String, ArrayList<String>>> getEnabledInputMethodsAndSubtypeListLocked() {
1047             return buildInputMethodsAndSubtypeList(getEnabledInputMethodsStr(),
1048                     mInputMethodSplitter,
1049                     mSubtypeSplitter);
1050         }
1051 
appendAndPutEnabledInputMethodLocked(String id, boolean reloadInputMethodStr)1052         public void appendAndPutEnabledInputMethodLocked(String id, boolean reloadInputMethodStr) {
1053             if (reloadInputMethodStr) {
1054                 getEnabledInputMethodsStr();
1055             }
1056             if (TextUtils.isEmpty(mEnabledInputMethodsStrCache)) {
1057                 // Add in the newly enabled input method.
1058                 putEnabledInputMethodsStr(id);
1059             } else {
1060                 putEnabledInputMethodsStr(
1061                         mEnabledInputMethodsStrCache + INPUT_METHOD_SEPARATOR + id);
1062             }
1063         }
1064 
1065         /**
1066          * Build and put a string of EnabledInputMethods with removing specified Id.
1067          * @return the specified id was removed or not.
1068          */
buildAndPutEnabledInputMethodsStrRemovingIdLocked( StringBuilder builder, List<Pair<String, ArrayList<String>>> imsList, String id)1069         public boolean buildAndPutEnabledInputMethodsStrRemovingIdLocked(
1070                 StringBuilder builder, List<Pair<String, ArrayList<String>>> imsList, String id) {
1071             boolean isRemoved = false;
1072             boolean needsAppendSeparator = false;
1073             for (Pair<String, ArrayList<String>> ims: imsList) {
1074                 String curId = ims.first;
1075                 if (curId.equals(id)) {
1076                     // We are disabling this input method, and it is
1077                     // currently enabled.  Skip it to remove from the
1078                     // new list.
1079                     isRemoved = true;
1080                 } else {
1081                     if (needsAppendSeparator) {
1082                         builder.append(INPUT_METHOD_SEPARATOR);
1083                     } else {
1084                         needsAppendSeparator = true;
1085                     }
1086                     buildEnabledInputMethodsSettingString(builder, ims);
1087                 }
1088             }
1089             if (isRemoved) {
1090                 // Update the setting with the new list of input methods.
1091                 putEnabledInputMethodsStr(builder.toString());
1092             }
1093             return isRemoved;
1094         }
1095 
createEnabledInputMethodListLocked( List<Pair<String, ArrayList<String>>> imsList)1096         private ArrayList<InputMethodInfo> createEnabledInputMethodListLocked(
1097                 List<Pair<String, ArrayList<String>>> imsList) {
1098             final ArrayList<InputMethodInfo> res = new ArrayList<>();
1099             for (Pair<String, ArrayList<String>> ims: imsList) {
1100                 InputMethodInfo info = mMethodMap.get(ims.first);
1101                 if (info != null && !info.isVrOnly()) {
1102                     res.add(info);
1103                 }
1104             }
1105             return res;
1106         }
1107 
putEnabledInputMethodsStr(@ullable String str)1108         private void putEnabledInputMethodsStr(@Nullable String str) {
1109             if (DEBUG) {
1110                 Slog.d(TAG, "putEnabledInputMethodStr: " + str);
1111             }
1112             if (TextUtils.isEmpty(str)) {
1113                 // OK to coalesce to null, since getEnabledInputMethodsStr() can take care of the
1114                 // empty data scenario.
1115                 putString(Settings.Secure.ENABLED_INPUT_METHODS, null);
1116             } else {
1117                 putString(Settings.Secure.ENABLED_INPUT_METHODS, str);
1118             }
1119             // TODO: Update callers of putEnabledInputMethodsStr to make str @NonNull.
1120             mEnabledInputMethodsStrCache = (str != null ? str : "");
1121         }
1122 
1123         @NonNull
getEnabledInputMethodsStr()1124         public String getEnabledInputMethodsStr() {
1125             mEnabledInputMethodsStrCache = getString(Settings.Secure.ENABLED_INPUT_METHODS, "");
1126             if (DEBUG) {
1127                 Slog.d(TAG, "getEnabledInputMethodsStr: " + mEnabledInputMethodsStrCache
1128                         + ", " + mCurrentUserId);
1129             }
1130             return mEnabledInputMethodsStrCache;
1131         }
1132 
saveSubtypeHistory( List<Pair<String, String>> savedImes, String newImeId, String newSubtypeId)1133         private void saveSubtypeHistory(
1134                 List<Pair<String, String>> savedImes, String newImeId, String newSubtypeId) {
1135             StringBuilder builder = new StringBuilder();
1136             boolean isImeAdded = false;
1137             if (!TextUtils.isEmpty(newImeId) && !TextUtils.isEmpty(newSubtypeId)) {
1138                 builder.append(newImeId).append(INPUT_METHOD_SUBTYPE_SEPARATOR).append(
1139                         newSubtypeId);
1140                 isImeAdded = true;
1141             }
1142             for (Pair<String, String> ime: savedImes) {
1143                 String imeId = ime.first;
1144                 String subtypeId = ime.second;
1145                 if (TextUtils.isEmpty(subtypeId)) {
1146                     subtypeId = NOT_A_SUBTYPE_ID_STR;
1147                 }
1148                 if (isImeAdded) {
1149                     builder.append(INPUT_METHOD_SEPARATOR);
1150                 } else {
1151                     isImeAdded = true;
1152                 }
1153                 builder.append(imeId).append(INPUT_METHOD_SUBTYPE_SEPARATOR).append(
1154                         subtypeId);
1155             }
1156             // Remove the last INPUT_METHOD_SEPARATOR
1157             putSubtypeHistoryStr(builder.toString());
1158         }
1159 
addSubtypeToHistory(String imeId, String subtypeId)1160         private void addSubtypeToHistory(String imeId, String subtypeId) {
1161             List<Pair<String, String>> subtypeHistory = loadInputMethodAndSubtypeHistoryLocked();
1162             for (Pair<String, String> ime: subtypeHistory) {
1163                 if (ime.first.equals(imeId)) {
1164                     if (DEBUG) {
1165                         Slog.v(TAG, "Subtype found in the history: " + imeId + ", "
1166                                 + ime.second);
1167                     }
1168                     // We should break here
1169                     subtypeHistory.remove(ime);
1170                     break;
1171                 }
1172             }
1173             if (DEBUG) {
1174                 Slog.v(TAG, "Add subtype to the history: " + imeId + ", " + subtypeId);
1175             }
1176             saveSubtypeHistory(subtypeHistory, imeId, subtypeId);
1177         }
1178 
putSubtypeHistoryStr(@onNull String str)1179         private void putSubtypeHistoryStr(@NonNull String str) {
1180             if (DEBUG) {
1181                 Slog.d(TAG, "putSubtypeHistoryStr: " + str);
1182             }
1183             if (TextUtils.isEmpty(str)) {
1184                 // OK to coalesce to null, since getSubtypeHistoryStr() can take care of the empty
1185                 // data scenario.
1186                 putString(Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY, null);
1187             } else {
1188                 putString(Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY, str);
1189             }
1190         }
1191 
getLastInputMethodAndSubtypeLocked()1192         public Pair<String, String> getLastInputMethodAndSubtypeLocked() {
1193             // Gets the first one from the history
1194             return getLastSubtypeForInputMethodLockedInternal(null);
1195         }
1196 
getLastSubtypeForInputMethodLocked(String imeId)1197         public String getLastSubtypeForInputMethodLocked(String imeId) {
1198             Pair<String, String> ime = getLastSubtypeForInputMethodLockedInternal(imeId);
1199             if (ime != null) {
1200                 return ime.second;
1201             } else {
1202                 return null;
1203             }
1204         }
1205 
getLastSubtypeForInputMethodLockedInternal(String imeId)1206         private Pair<String, String> getLastSubtypeForInputMethodLockedInternal(String imeId) {
1207             List<Pair<String, ArrayList<String>>> enabledImes =
1208                     getEnabledInputMethodsAndSubtypeListLocked();
1209             List<Pair<String, String>> subtypeHistory = loadInputMethodAndSubtypeHistoryLocked();
1210             for (Pair<String, String> imeAndSubtype : subtypeHistory) {
1211                 final String imeInTheHistory = imeAndSubtype.first;
1212                 // If imeId is empty, returns the first IME and subtype in the history
1213                 if (TextUtils.isEmpty(imeId) || imeInTheHistory.equals(imeId)) {
1214                     final String subtypeInTheHistory = imeAndSubtype.second;
1215                     final String subtypeHashCode =
1216                             getEnabledSubtypeHashCodeForInputMethodAndSubtypeLocked(
1217                                     enabledImes, imeInTheHistory, subtypeInTheHistory);
1218                     if (!TextUtils.isEmpty(subtypeHashCode)) {
1219                         if (DEBUG) {
1220                             Slog.d(TAG, "Enabled subtype found in the history: " + subtypeHashCode);
1221                         }
1222                         return new Pair<>(imeInTheHistory, subtypeHashCode);
1223                     }
1224                 }
1225             }
1226             if (DEBUG) {
1227                 Slog.d(TAG, "No enabled IME found in the history");
1228             }
1229             return null;
1230         }
1231 
getEnabledSubtypeHashCodeForInputMethodAndSubtypeLocked(List<Pair<String, ArrayList<String>>> enabledImes, String imeId, String subtypeHashCode)1232         private String getEnabledSubtypeHashCodeForInputMethodAndSubtypeLocked(List<Pair<String,
1233                 ArrayList<String>>> enabledImes, String imeId, String subtypeHashCode) {
1234             for (Pair<String, ArrayList<String>> enabledIme: enabledImes) {
1235                 if (enabledIme.first.equals(imeId)) {
1236                     final ArrayList<String> explicitlyEnabledSubtypes = enabledIme.second;
1237                     final InputMethodInfo imi = mMethodMap.get(imeId);
1238                     if (explicitlyEnabledSubtypes.size() == 0) {
1239                         // If there are no explicitly enabled subtypes, applicable subtypes are
1240                         // enabled implicitly.
1241                         // If IME is enabled and no subtypes are enabled, applicable subtypes
1242                         // are enabled implicitly, so needs to treat them to be enabled.
1243                         if (imi != null && imi.getSubtypeCount() > 0) {
1244                             List<InputMethodSubtype> implicitlySelectedSubtypes =
1245                                     getImplicitlyApplicableSubtypesLocked(mRes, imi);
1246                             if (implicitlySelectedSubtypes != null) {
1247                                 final int N = implicitlySelectedSubtypes.size();
1248                                 for (int i = 0; i < N; ++i) {
1249                                     final InputMethodSubtype st = implicitlySelectedSubtypes.get(i);
1250                                     if (String.valueOf(st.hashCode()).equals(subtypeHashCode)) {
1251                                         return subtypeHashCode;
1252                                     }
1253                                 }
1254                             }
1255                         }
1256                     } else {
1257                         for (String s: explicitlyEnabledSubtypes) {
1258                             if (s.equals(subtypeHashCode)) {
1259                                 // If both imeId and subtypeId are enabled, return subtypeId.
1260                                 try {
1261                                     final int hashCode = Integer.parseInt(subtypeHashCode);
1262                                     // Check whether the subtype id is valid or not
1263                                     if (isValidSubtypeId(imi, hashCode)) {
1264                                         return s;
1265                                     } else {
1266                                         return NOT_A_SUBTYPE_ID_STR;
1267                                     }
1268                                 } catch (NumberFormatException e) {
1269                                     return NOT_A_SUBTYPE_ID_STR;
1270                                 }
1271                             }
1272                         }
1273                     }
1274                     // If imeId was enabled but subtypeId was disabled.
1275                     return NOT_A_SUBTYPE_ID_STR;
1276                 }
1277             }
1278             // If both imeId and subtypeId are disabled, return null
1279             return null;
1280         }
1281 
loadInputMethodAndSubtypeHistoryLocked()1282         private List<Pair<String, String>> loadInputMethodAndSubtypeHistoryLocked() {
1283             ArrayList<Pair<String, String>> imsList = new ArrayList<>();
1284             final String subtypeHistoryStr = getSubtypeHistoryStr();
1285             if (TextUtils.isEmpty(subtypeHistoryStr)) {
1286                 return imsList;
1287             }
1288             mInputMethodSplitter.setString(subtypeHistoryStr);
1289             while (mInputMethodSplitter.hasNext()) {
1290                 String nextImsStr = mInputMethodSplitter.next();
1291                 mSubtypeSplitter.setString(nextImsStr);
1292                 if (mSubtypeSplitter.hasNext()) {
1293                     String subtypeId = NOT_A_SUBTYPE_ID_STR;
1294                     // The first element is ime id.
1295                     String imeId = mSubtypeSplitter.next();
1296                     while (mSubtypeSplitter.hasNext()) {
1297                         subtypeId = mSubtypeSplitter.next();
1298                         break;
1299                     }
1300                     imsList.add(new Pair<>(imeId, subtypeId));
1301                 }
1302             }
1303             return imsList;
1304         }
1305 
1306         @NonNull
getSubtypeHistoryStr()1307         private String getSubtypeHistoryStr() {
1308             final String history = getString(Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY, "");
1309             if (DEBUG) {
1310                 Slog.d(TAG, "getSubtypeHistoryStr: " + history);
1311             }
1312             return history;
1313         }
1314 
putSelectedInputMethod(String imeId)1315         public void putSelectedInputMethod(String imeId) {
1316             if (DEBUG) {
1317                 Slog.d(TAG, "putSelectedInputMethodStr: " + imeId + ", "
1318                         + mCurrentUserId);
1319             }
1320             putString(Settings.Secure.DEFAULT_INPUT_METHOD, imeId);
1321         }
1322 
putSelectedSubtype(int subtypeId)1323         public void putSelectedSubtype(int subtypeId) {
1324             if (DEBUG) {
1325                 Slog.d(TAG, "putSelectedInputMethodSubtypeStr: " + subtypeId + ", "
1326                         + mCurrentUserId);
1327             }
1328             putInt(Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE, subtypeId);
1329         }
1330 
1331         @Nullable
getSelectedInputMethod()1332         public String getSelectedInputMethod() {
1333             final String imi = getString(Settings.Secure.DEFAULT_INPUT_METHOD, null);
1334             if (DEBUG) {
1335                 Slog.d(TAG, "getSelectedInputMethodStr: " + imi);
1336             }
1337             return imi;
1338         }
1339 
isSubtypeSelected()1340         public boolean isSubtypeSelected() {
1341             return getSelectedInputMethodSubtypeHashCode() != NOT_A_SUBTYPE_ID;
1342         }
1343 
getSelectedInputMethodSubtypeHashCode()1344         private int getSelectedInputMethodSubtypeHashCode() {
1345             return getInt(Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE, NOT_A_SUBTYPE_ID);
1346         }
1347 
isShowImeWithHardKeyboardEnabled()1348         public boolean isShowImeWithHardKeyboardEnabled() {
1349             return getBoolean(Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD, false);
1350         }
1351 
setShowImeWithHardKeyboard(boolean show)1352         public void setShowImeWithHardKeyboard(boolean show) {
1353             putBoolean(Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD, show);
1354         }
1355 
1356         @UserIdInt
getCurrentUserId()1357         public int getCurrentUserId() {
1358             return mCurrentUserId;
1359         }
1360 
getSelectedInputMethodSubtypeId(String selectedImiId)1361         public int getSelectedInputMethodSubtypeId(String selectedImiId) {
1362             final InputMethodInfo imi = mMethodMap.get(selectedImiId);
1363             if (imi == null) {
1364                 return NOT_A_SUBTYPE_ID;
1365             }
1366             final int subtypeHashCode = getSelectedInputMethodSubtypeHashCode();
1367             return getSubtypeIdFromHashCode(imi, subtypeHashCode);
1368         }
1369 
saveCurrentInputMethodAndSubtypeToHistory( String curMethodId, InputMethodSubtype currentSubtype)1370         public void saveCurrentInputMethodAndSubtypeToHistory(
1371                 String curMethodId, InputMethodSubtype currentSubtype) {
1372             String subtypeId = NOT_A_SUBTYPE_ID_STR;
1373             if (currentSubtype != null) {
1374                 subtypeId = String.valueOf(currentSubtype.hashCode());
1375             }
1376             if (canAddToLastInputMethod(currentSubtype)) {
1377                 addSubtypeToHistory(curMethodId, subtypeId);
1378             }
1379         }
1380 
1381         public HashMap<InputMethodInfo, List<InputMethodSubtype>>
getExplicitlyOrImplicitlyEnabledInputMethodsAndSubtypeListLocked(Context context)1382                 getExplicitlyOrImplicitlyEnabledInputMethodsAndSubtypeListLocked(Context context) {
1383             HashMap<InputMethodInfo, List<InputMethodSubtype>> enabledInputMethodAndSubtypes =
1384                     new HashMap<>();
1385             for (InputMethodInfo imi: getEnabledInputMethodListLocked()) {
1386                 enabledInputMethodAndSubtypes.put(
1387                         imi, getEnabledInputMethodSubtypeListLocked(context, imi, true));
1388             }
1389             return enabledInputMethodAndSubtypes;
1390         }
1391 
dumpLocked(final Printer pw, final String prefix)1392         public void dumpLocked(final Printer pw, final String prefix) {
1393             pw.println(prefix + "mCurrentUserId=" + mCurrentUserId);
1394             pw.println(prefix + "mCurrentProfileIds=" + Arrays.toString(mCurrentProfileIds));
1395             pw.println(prefix + "mCopyOnWrite=" + mCopyOnWrite);
1396             pw.println(prefix + "mEnabledInputMethodsStrCache=" + mEnabledInputMethodsStrCache);
1397         }
1398     }
1399 
1400     // For spell checker service manager.
1401     // TODO: Should we have TextServicesUtils.java?
1402     private static final Locale LOCALE_EN_US = new Locale("en", "US");
1403     private static final Locale LOCALE_EN_GB = new Locale("en", "GB");
1404 
1405     /**
1406      * Returns a list of {@link Locale} in the order of appropriateness for the default spell
1407      * checker service.
1408      *
1409      * <p>If the system language is English, and the region is also explicitly specified in the
1410      * system locale, the following fallback order will be applied.</p>
1411      * <ul>
1412      * <li>(system-locale-language, system-locale-region, system-locale-variant) (if exists)</li>
1413      * <li>(system-locale-language, system-locale-region)</li>
1414      * <li>("en", "US")</li>
1415      * <li>("en", "GB")</li>
1416      * <li>("en")</li>
1417      * </ul>
1418      *
1419      * <p>If the system language is English, but no region is specified in the system locale,
1420      * the following fallback order will be applied.</p>
1421      * <ul>
1422      * <li>("en")</li>
1423      * <li>("en", "US")</li>
1424      * <li>("en", "GB")</li>
1425      * </ul>
1426      *
1427      * <p>If the system language is not English, the following fallback order will be applied.</p>
1428      * <ul>
1429      * <li>(system-locale-language, system-locale-region, system-locale-variant) (if exists)</li>
1430      * <li>(system-locale-language, system-locale-region) (if exists)</li>
1431      * <li>(system-locale-language) (if exists)</li>
1432      * <li>("en", "US")</li>
1433      * <li>("en", "GB")</li>
1434      * <li>("en")</li>
1435      * </ul>
1436      *
1437      * @param systemLocale the current system locale to be taken into consideration.
1438      * @return a list of {@link Locale}. The first one is considered to be most appropriate.
1439      */
1440     @VisibleForTesting
getSuitableLocalesForSpellChecker( @ullable final Locale systemLocale)1441     public static ArrayList<Locale> getSuitableLocalesForSpellChecker(
1442             @Nullable final Locale systemLocale) {
1443         final Locale systemLocaleLanguageCountryVariant;
1444         final Locale systemLocaleLanguageCountry;
1445         final Locale systemLocaleLanguage;
1446         if (systemLocale != null) {
1447             final String language = systemLocale.getLanguage();
1448             final boolean hasLanguage = !TextUtils.isEmpty(language);
1449             final String country = systemLocale.getCountry();
1450             final boolean hasCountry = !TextUtils.isEmpty(country);
1451             final String variant = systemLocale.getVariant();
1452             final boolean hasVariant = !TextUtils.isEmpty(variant);
1453             if (hasLanguage && hasCountry && hasVariant) {
1454                 systemLocaleLanguageCountryVariant = new Locale(language, country, variant);
1455             } else {
1456                 systemLocaleLanguageCountryVariant = null;
1457             }
1458             if (hasLanguage && hasCountry) {
1459                 systemLocaleLanguageCountry = new Locale(language, country);
1460             } else {
1461                 systemLocaleLanguageCountry = null;
1462             }
1463             if (hasLanguage) {
1464                 systemLocaleLanguage = new Locale(language);
1465             } else {
1466                 systemLocaleLanguage = null;
1467             }
1468         } else {
1469             systemLocaleLanguageCountryVariant = null;
1470             systemLocaleLanguageCountry = null;
1471             systemLocaleLanguage = null;
1472         }
1473 
1474         final ArrayList<Locale> locales = new ArrayList<>();
1475         if (systemLocaleLanguageCountryVariant != null) {
1476             locales.add(systemLocaleLanguageCountryVariant);
1477         }
1478 
1479         if (Locale.ENGLISH.equals(systemLocaleLanguage)) {
1480             if (systemLocaleLanguageCountry != null) {
1481                 // If the system language is English, and the region is also explicitly specified,
1482                 // following fallback order will be applied.
1483                 // - systemLocaleLanguageCountry [if systemLocaleLanguageCountry is non-null]
1484                 // - en_US [if systemLocaleLanguageCountry is non-null and not en_US]
1485                 // - en_GB [if systemLocaleLanguageCountry is non-null and not en_GB]
1486                 // - en
1487                 if (systemLocaleLanguageCountry != null) {
1488                     locales.add(systemLocaleLanguageCountry);
1489                 }
1490                 if (!LOCALE_EN_US.equals(systemLocaleLanguageCountry)) {
1491                     locales.add(LOCALE_EN_US);
1492                 }
1493                 if (!LOCALE_EN_GB.equals(systemLocaleLanguageCountry)) {
1494                     locales.add(LOCALE_EN_GB);
1495                 }
1496                 locales.add(Locale.ENGLISH);
1497             } else {
1498                 // If the system language is English, but no region is specified, following
1499                 // fallback order will be applied.
1500                 // - en
1501                 // - en_US
1502                 // - en_GB
1503                 locales.add(Locale.ENGLISH);
1504                 locales.add(LOCALE_EN_US);
1505                 locales.add(LOCALE_EN_GB);
1506             }
1507         } else {
1508             // If the system language is not English, the fallback order will be
1509             // - systemLocaleLanguageCountry  [if non-null]
1510             // - systemLocaleLanguage  [if non-null]
1511             // - en_US
1512             // - en_GB
1513             // - en
1514             if (systemLocaleLanguageCountry != null) {
1515                 locales.add(systemLocaleLanguageCountry);
1516             }
1517             if (systemLocaleLanguage != null) {
1518                 locales.add(systemLocaleLanguage);
1519             }
1520             locales.add(LOCALE_EN_US);
1521             locales.add(LOCALE_EN_GB);
1522             locales.add(Locale.ENGLISH);
1523         }
1524         return locales;
1525     }
1526 
isSoftInputModeStateVisibleAllowed( int targetSdkVersion, int controlFlags)1527     public static boolean isSoftInputModeStateVisibleAllowed(
1528             int targetSdkVersion, int controlFlags) {
1529         if (targetSdkVersion < Build.VERSION_CODES.P) {
1530             // for compatibility.
1531             return true;
1532         }
1533         if ((controlFlags & CONTROL_WINDOW_VIEW_HAS_FOCUS) == 0) {
1534             return false;
1535         }
1536         if ((controlFlags & CONTROL_WINDOW_IS_TEXT_EDITOR) == 0) {
1537             return false;
1538         }
1539         return true;
1540     }
1541 
1542 }
1543