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