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