1 /* 2 * Copyright (C) 2013 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.inputmethod; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.annotation.UserIdInt; 22 import android.app.AppOpsManager; 23 import android.content.ContentResolver; 24 import android.content.Context; 25 import android.content.pm.ApplicationInfo; 26 import android.content.pm.IPackageManager; 27 import android.content.pm.PackageManager; 28 import android.content.res.Resources; 29 import android.os.Build; 30 import android.os.LocaleList; 31 import android.os.RemoteException; 32 import android.os.UserHandle; 33 import android.os.UserManagerInternal; 34 import android.provider.Settings; 35 import android.text.TextUtils; 36 import android.util.ArrayMap; 37 import android.util.ArraySet; 38 import android.util.Pair; 39 import android.util.Printer; 40 import android.util.Slog; 41 import android.view.inputmethod.InputMethodInfo; 42 import android.view.inputmethod.InputMethodSubtype; 43 import android.view.textservice.SpellCheckerInfo; 44 45 import com.android.internal.annotations.GuardedBy; 46 import com.android.internal.annotations.VisibleForTesting; 47 import com.android.internal.inputmethod.StartInputFlags; 48 import com.android.server.LocalServices; 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 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 containsSubtypeOf(InputMethodInfo imi, @Nullable Locale locale, boolean checkCountry, String mode)340 static boolean containsSubtypeOf(InputMethodInfo imi, @Nullable Locale locale, 341 boolean checkCountry, String mode) { 342 if (locale == null) { 343 return false; 344 } 345 final int N = imi.getSubtypeCount(); 346 for (int i = 0; i < N; ++i) { 347 final InputMethodSubtype subtype = imi.getSubtypeAt(i); 348 if (checkCountry) { 349 final Locale subtypeLocale = subtype.getLocaleObject(); 350 if (subtypeLocale == null || 351 !TextUtils.equals(subtypeLocale.getLanguage(), locale.getLanguage()) || 352 !TextUtils.equals(subtypeLocale.getCountry(), locale.getCountry())) { 353 continue; 354 } 355 } else { 356 final Locale subtypeLocale = new Locale(getLanguageFromLocaleString( 357 subtype.getLocale())); 358 if (!TextUtils.equals(subtypeLocale.getLanguage(), locale.getLanguage())) { 359 continue; 360 } 361 } 362 if (mode == SUBTYPE_MODE_ANY || TextUtils.isEmpty(mode) || 363 mode.equalsIgnoreCase(subtype.getMode())) { 364 return true; 365 } 366 } 367 return false; 368 } 369 getSubtypes(InputMethodInfo imi)370 static ArrayList<InputMethodSubtype> getSubtypes(InputMethodInfo imi) { 371 ArrayList<InputMethodSubtype> subtypes = new ArrayList<>(); 372 final int subtypeCount = imi.getSubtypeCount(); 373 for (int i = 0; i < subtypeCount; ++i) { 374 subtypes.add(imi.getSubtypeAt(i)); 375 } 376 return subtypes; 377 } 378 getMostApplicableDefaultIME(List<InputMethodInfo> enabledImes)379 static InputMethodInfo getMostApplicableDefaultIME(List<InputMethodInfo> enabledImes) { 380 if (enabledImes == null || enabledImes.isEmpty()) { 381 return null; 382 } 383 // We'd prefer to fall back on a system IME, since that is safer. 384 int i = enabledImes.size(); 385 int firstFoundSystemIme = -1; 386 while (i > 0) { 387 i--; 388 final InputMethodInfo imi = enabledImes.get(i); 389 if (imi.isAuxiliaryIme()) { 390 continue; 391 } 392 if (imi.isSystem() && containsSubtypeOf( 393 imi, ENGLISH_LOCALE, false /* checkCountry */, SUBTYPE_MODE_KEYBOARD)) { 394 return imi; 395 } 396 if (firstFoundSystemIme < 0 && imi.isSystem()) { 397 firstFoundSystemIme = i; 398 } 399 } 400 return enabledImes.get(Math.max(firstFoundSystemIme, 0)); 401 } 402 isValidSubtypeId(InputMethodInfo imi, int subtypeHashCode)403 static boolean isValidSubtypeId(InputMethodInfo imi, int subtypeHashCode) { 404 return getSubtypeIdFromHashCode(imi, subtypeHashCode) != NOT_A_SUBTYPE_ID; 405 } 406 getSubtypeIdFromHashCode(InputMethodInfo imi, int subtypeHashCode)407 static int getSubtypeIdFromHashCode(InputMethodInfo imi, int subtypeHashCode) { 408 if (imi != null) { 409 final int subtypeCount = imi.getSubtypeCount(); 410 for (int i = 0; i < subtypeCount; ++i) { 411 InputMethodSubtype ims = imi.getSubtypeAt(i); 412 if (subtypeHashCode == ims.hashCode()) { 413 return i; 414 } 415 } 416 } 417 return NOT_A_SUBTYPE_ID; 418 } 419 420 private static final LocaleUtils.LocaleExtractor<InputMethodSubtype> sSubtypeToLocale = 421 new LocaleUtils.LocaleExtractor<InputMethodSubtype>() { 422 @Override 423 public Locale get(InputMethodSubtype source) { 424 return source != null ? source.getLocaleObject() : null; 425 } 426 }; 427 428 @VisibleForTesting getImplicitlyApplicableSubtypesLocked( Resources res, InputMethodInfo imi)429 static ArrayList<InputMethodSubtype> getImplicitlyApplicableSubtypesLocked( 430 Resources res, InputMethodInfo imi) { 431 final LocaleList systemLocales = res.getConfiguration().getLocales(); 432 433 synchronized (sCacheLock) { 434 // We intentionally do not use InputMethodInfo#equals(InputMethodInfo) here because 435 // it does not check if subtypes are also identical. 436 if (systemLocales.equals(sCachedSystemLocales) && sCachedInputMethodInfo == imi) { 437 return new ArrayList<>(sCachedResult); 438 } 439 } 440 441 // Note: Only resource info in "res" is used in getImplicitlyApplicableSubtypesLockedImpl(). 442 // TODO: Refactor getImplicitlyApplicableSubtypesLockedImpl() so that it can receive 443 // LocaleList rather than Resource. 444 final ArrayList<InputMethodSubtype> result = 445 getImplicitlyApplicableSubtypesLockedImpl(res, imi); 446 synchronized (sCacheLock) { 447 // Both LocaleList and InputMethodInfo are immutable. No need to copy them here. 448 sCachedSystemLocales = systemLocales; 449 sCachedInputMethodInfo = imi; 450 sCachedResult = new ArrayList<>(result); 451 } 452 return result; 453 } 454 getImplicitlyApplicableSubtypesLockedImpl( Resources res, InputMethodInfo imi)455 private static ArrayList<InputMethodSubtype> getImplicitlyApplicableSubtypesLockedImpl( 456 Resources res, InputMethodInfo imi) { 457 final List<InputMethodSubtype> subtypes = InputMethodUtils.getSubtypes(imi); 458 final LocaleList systemLocales = res.getConfiguration().getLocales(); 459 final String systemLocale = systemLocales.get(0).toString(); 460 if (TextUtils.isEmpty(systemLocale)) return new ArrayList<>(); 461 final int numSubtypes = subtypes.size(); 462 463 // Handle overridesImplicitlyEnabledSubtype mechanism. 464 final ArrayMap<String, InputMethodSubtype> applicableModeAndSubtypesMap = new ArrayMap<>(); 465 for (int i = 0; i < numSubtypes; ++i) { 466 // scan overriding implicitly enabled subtypes. 467 final InputMethodSubtype subtype = subtypes.get(i); 468 if (subtype.overridesImplicitlyEnabledSubtype()) { 469 final String mode = subtype.getMode(); 470 if (!applicableModeAndSubtypesMap.containsKey(mode)) { 471 applicableModeAndSubtypesMap.put(mode, subtype); 472 } 473 } 474 } 475 if (applicableModeAndSubtypesMap.size() > 0) { 476 return new ArrayList<>(applicableModeAndSubtypesMap.values()); 477 } 478 479 final ArrayMap<String, ArrayList<InputMethodSubtype>> nonKeyboardSubtypesMap = 480 new ArrayMap<>(); 481 final ArrayList<InputMethodSubtype> keyboardSubtypes = new ArrayList<>(); 482 483 for (int i = 0; i < numSubtypes; ++i) { 484 final InputMethodSubtype subtype = subtypes.get(i); 485 final String mode = subtype.getMode(); 486 if (SUBTYPE_MODE_KEYBOARD.equals(mode)) { 487 keyboardSubtypes.add(subtype); 488 } else { 489 if (!nonKeyboardSubtypesMap.containsKey(mode)) { 490 nonKeyboardSubtypesMap.put(mode, new ArrayList<>()); 491 } 492 nonKeyboardSubtypesMap.get(mode).add(subtype); 493 } 494 } 495 496 final ArrayList<InputMethodSubtype> applicableSubtypes = new ArrayList<>(); 497 LocaleUtils.filterByLanguage(keyboardSubtypes, sSubtypeToLocale, systemLocales, 498 applicableSubtypes); 499 500 if (!applicableSubtypes.isEmpty()) { 501 boolean hasAsciiCapableKeyboard = false; 502 final int numApplicationSubtypes = applicableSubtypes.size(); 503 for (int i = 0; i < numApplicationSubtypes; ++i) { 504 final InputMethodSubtype subtype = applicableSubtypes.get(i); 505 if (subtype.isAsciiCapable()) { 506 hasAsciiCapableKeyboard = true; 507 break; 508 } 509 } 510 if (!hasAsciiCapableKeyboard) { 511 final int numKeyboardSubtypes = keyboardSubtypes.size(); 512 for (int i = 0; i < numKeyboardSubtypes; ++i) { 513 final InputMethodSubtype subtype = keyboardSubtypes.get(i); 514 final String mode = subtype.getMode(); 515 if (SUBTYPE_MODE_KEYBOARD.equals(mode) && subtype.containsExtraValueKey( 516 TAG_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE)) { 517 applicableSubtypes.add(subtype); 518 } 519 } 520 } 521 } 522 523 if (applicableSubtypes.isEmpty()) { 524 InputMethodSubtype lastResortKeyboardSubtype = findLastResortApplicableSubtypeLocked( 525 res, subtypes, SUBTYPE_MODE_KEYBOARD, systemLocale, true); 526 if (lastResortKeyboardSubtype != null) { 527 applicableSubtypes.add(lastResortKeyboardSubtype); 528 } 529 } 530 531 // For each non-keyboard mode, extract subtypes with system locales. 532 for (final ArrayList<InputMethodSubtype> subtypeList : nonKeyboardSubtypesMap.values()) { 533 LocaleUtils.filterByLanguage(subtypeList, sSubtypeToLocale, systemLocales, 534 applicableSubtypes); 535 } 536 537 return applicableSubtypes; 538 } 539 540 /** 541 * Returns the language component of a given locale string. 542 * TODO: Use {@link Locale#toLanguageTag()} and {@link Locale#forLanguageTag(String)} 543 */ getLanguageFromLocaleString(String locale)544 private static String getLanguageFromLocaleString(String locale) { 545 final int idx = locale.indexOf('_'); 546 if (idx < 0) { 547 return locale; 548 } else { 549 return locale.substring(0, idx); 550 } 551 } 552 553 /** 554 * If there are no selected subtypes, tries finding the most applicable one according to the 555 * given locale. 556 * @param subtypes this function will search the most applicable subtype in subtypes 557 * @param mode subtypes will be filtered by mode 558 * @param locale subtypes will be filtered by locale 559 * @param canIgnoreLocaleAsLastResort if this function can't find the most applicable subtype, 560 * it will return the first subtype matched with mode 561 * @return the most applicable subtypeId 562 */ findLastResortApplicableSubtypeLocked( Resources res, List<InputMethodSubtype> subtypes, String mode, String locale, boolean canIgnoreLocaleAsLastResort)563 static InputMethodSubtype findLastResortApplicableSubtypeLocked( 564 Resources res, List<InputMethodSubtype> subtypes, String mode, String locale, 565 boolean canIgnoreLocaleAsLastResort) { 566 if (subtypes == null || subtypes.size() == 0) { 567 return null; 568 } 569 if (TextUtils.isEmpty(locale)) { 570 locale = res.getConfiguration().locale.toString(); 571 } 572 final String language = getLanguageFromLocaleString(locale); 573 boolean partialMatchFound = false; 574 InputMethodSubtype applicableSubtype = null; 575 InputMethodSubtype firstMatchedModeSubtype = null; 576 final int N = subtypes.size(); 577 for (int i = 0; i < N; ++i) { 578 InputMethodSubtype subtype = subtypes.get(i); 579 final String subtypeLocale = subtype.getLocale(); 580 final String subtypeLanguage = getLanguageFromLocaleString(subtypeLocale); 581 // An applicable subtype should match "mode". If mode is null, mode will be ignored, 582 // and all subtypes with all modes can be candidates. 583 if (mode == null || subtypes.get(i).getMode().equalsIgnoreCase(mode)) { 584 if (firstMatchedModeSubtype == null) { 585 firstMatchedModeSubtype = subtype; 586 } 587 if (locale.equals(subtypeLocale)) { 588 // Exact match (e.g. system locale is "en_US" and subtype locale is "en_US") 589 applicableSubtype = subtype; 590 break; 591 } else if (!partialMatchFound && language.equals(subtypeLanguage)) { 592 // Partial match (e.g. system locale is "en_US" and subtype locale is "en") 593 applicableSubtype = subtype; 594 partialMatchFound = true; 595 } 596 } 597 } 598 599 if (applicableSubtype == null && canIgnoreLocaleAsLastResort) { 600 return firstMatchedModeSubtype; 601 } 602 603 // The first subtype applicable to the system locale will be defined as the most applicable 604 // subtype. 605 if (DEBUG) { 606 if (applicableSubtype != null) { 607 Slog.d(TAG, "Applicable InputMethodSubtype was found: " 608 + applicableSubtype.getMode() + "," + applicableSubtype.getLocale()); 609 } 610 } 611 return applicableSubtype; 612 } 613 canAddToLastInputMethod(InputMethodSubtype subtype)614 static boolean canAddToLastInputMethod(InputMethodSubtype subtype) { 615 if (subtype == null) return true; 616 return !subtype.isAuxiliary(); 617 } 618 setNonSelectedSystemImesDisabledUntilUsed(IPackageManager packageManager, List<InputMethodInfo> enabledImis, @UserIdInt int userId, String callingPackage)619 static void setNonSelectedSystemImesDisabledUntilUsed(IPackageManager packageManager, 620 List<InputMethodInfo> enabledImis, @UserIdInt int userId, String callingPackage) { 621 if (DEBUG) { 622 Slog.d(TAG, "setNonSelectedSystemImesDisabledUntilUsed"); 623 } 624 final String[] systemImesDisabledUntilUsed = Resources.getSystem().getStringArray( 625 com.android.internal.R.array.config_disabledUntilUsedPreinstalledImes); 626 if (systemImesDisabledUntilUsed == null || systemImesDisabledUntilUsed.length == 0) { 627 return; 628 } 629 // Only the current spell checker should be treated as an enabled one. 630 final SpellCheckerInfo currentSpellChecker = 631 TextServicesManagerInternal.get().getCurrentSpellCheckerForUser(userId); 632 for (final String packageName : systemImesDisabledUntilUsed) { 633 if (DEBUG) { 634 Slog.d(TAG, "check " + packageName); 635 } 636 boolean enabledIme = false; 637 for (int j = 0; j < enabledImis.size(); ++j) { 638 final InputMethodInfo imi = enabledImis.get(j); 639 if (packageName.equals(imi.getPackageName())) { 640 enabledIme = true; 641 break; 642 } 643 } 644 if (enabledIme) { 645 // enabled ime. skip 646 continue; 647 } 648 if (currentSpellChecker != null 649 && packageName.equals(currentSpellChecker.getPackageName())) { 650 // enabled spell checker. skip 651 if (DEBUG) { 652 Slog.d(TAG, packageName + " is the current spell checker. skip"); 653 } 654 continue; 655 } 656 ApplicationInfo ai = null; 657 try { 658 ai = packageManager.getApplicationInfo(packageName, 659 PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS, userId); 660 } catch (RemoteException e) { 661 Slog.w(TAG, "getApplicationInfo failed. packageName=" + packageName 662 + " userId=" + userId, e); 663 continue; 664 } 665 if (ai == null) { 666 // No app found for packageName 667 continue; 668 } 669 final boolean isSystemPackage = (ai.flags & ApplicationInfo.FLAG_SYSTEM) != 0; 670 if (!isSystemPackage) { 671 continue; 672 } 673 setDisabledUntilUsed(packageManager, packageName, userId, callingPackage); 674 } 675 } 676 setDisabledUntilUsed(IPackageManager packageManager, String packageName, int userId, String callingPackage)677 private static void setDisabledUntilUsed(IPackageManager packageManager, String packageName, 678 int userId, String callingPackage) { 679 final int state; 680 try { 681 state = packageManager.getApplicationEnabledSetting(packageName, userId); 682 } catch (RemoteException e) { 683 Slog.w(TAG, "getApplicationEnabledSetting failed. packageName=" + packageName 684 + " userId=" + userId, e); 685 return; 686 } 687 if (state == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT 688 || state == PackageManager.COMPONENT_ENABLED_STATE_ENABLED) { 689 if (DEBUG) { 690 Slog.d(TAG, "Update state(" + packageName + "): DISABLED_UNTIL_USED"); 691 } 692 try { 693 packageManager.setApplicationEnabledSetting(packageName, 694 PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED, 695 0 /* newState */, userId, callingPackage); 696 } catch (RemoteException e) { 697 Slog.w(TAG, "setApplicationEnabledSetting failed. packageName=" + packageName 698 + " userId=" + userId + " callingPackage=" + callingPackage, e); 699 return; 700 } 701 } else { 702 if (DEBUG) { 703 Slog.d(TAG, packageName + " is already DISABLED_UNTIL_USED"); 704 } 705 } 706 } 707 getImeAndSubtypeDisplayName(Context context, InputMethodInfo imi, InputMethodSubtype subtype)708 static CharSequence getImeAndSubtypeDisplayName(Context context, InputMethodInfo imi, 709 InputMethodSubtype subtype) { 710 final CharSequence imiLabel = imi.loadLabel(context.getPackageManager()); 711 return subtype != null 712 ? TextUtils.concat(subtype.getDisplayName(context, 713 imi.getPackageName(), imi.getServiceInfo().applicationInfo), 714 (TextUtils.isEmpty(imiLabel) ? 715 "" : " - " + imiLabel)) 716 : imiLabel; 717 } 718 719 /** 720 * Returns true if a package name belongs to a UID. 721 * 722 * <p>This is a simple wrapper of {@link AppOpsManager#checkPackage(int, String)}.</p> 723 * @param appOpsManager the {@link AppOpsManager} object to be used for the validation. 724 * @param uid the UID to be validated. 725 * @param packageName the package name. 726 * @return {@code true} if the package name belongs to the UID. 727 */ checkIfPackageBelongsToUid(AppOpsManager appOpsManager, @UserIdInt int uid, String packageName)728 static boolean checkIfPackageBelongsToUid(AppOpsManager appOpsManager, 729 @UserIdInt int uid, String packageName) { 730 try { 731 appOpsManager.checkPackage(uid, packageName); 732 return true; 733 } catch (SecurityException e) { 734 return false; 735 } 736 } 737 738 /** 739 * Utility class for putting and getting settings for InputMethod 740 * TODO: Move all putters and getters of settings to this class. 741 */ 742 public static class InputMethodSettings { 743 private final TextUtils.SimpleStringSplitter mInputMethodSplitter = 744 new TextUtils.SimpleStringSplitter(INPUT_METHOD_SEPARATOR); 745 746 private final TextUtils.SimpleStringSplitter mSubtypeSplitter = 747 new TextUtils.SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATOR); 748 749 private final Resources mRes; 750 private final ContentResolver mResolver; 751 private final ArrayMap<String, InputMethodInfo> mMethodMap; 752 753 /** 754 * On-memory data store to emulate when {@link #mCopyOnWrite} is {@code true}. 755 */ 756 private final ArrayMap<String, String> mCopyOnWriteDataStore = new ArrayMap<>(); 757 758 private static final ArraySet<String> CLONE_TO_MANAGED_PROFILE = new ArraySet<>(); 759 static { 760 Settings.Secure.getCloneToManagedProfileSettings(CLONE_TO_MANAGED_PROFILE); 761 } 762 763 private static final UserManagerInternal sUserManagerInternal = 764 LocalServices.getService(UserManagerInternal.class); 765 766 private boolean mCopyOnWrite = false; 767 @NonNull 768 private String mEnabledInputMethodsStrCache = ""; 769 @UserIdInt 770 private int mCurrentUserId; 771 private int[] mCurrentProfileIds = new int[0]; 772 buildEnabledInputMethodsSettingString( StringBuilder builder, Pair<String, ArrayList<String>> ime)773 private static void buildEnabledInputMethodsSettingString( 774 StringBuilder builder, Pair<String, ArrayList<String>> ime) { 775 builder.append(ime.first); 776 // Inputmethod and subtypes are saved in the settings as follows: 777 // ime0;subtype0;subtype1:ime1;subtype0:ime2:ime3;subtype0;subtype1 778 for (String subtypeId: ime.second) { 779 builder.append(INPUT_METHOD_SUBTYPE_SEPARATOR).append(subtypeId); 780 } 781 } 782 buildInputMethodsAndSubtypeList( String enabledInputMethodsStr, TextUtils.SimpleStringSplitter inputMethodSplitter, TextUtils.SimpleStringSplitter subtypeSplitter)783 private static List<Pair<String, ArrayList<String>>> buildInputMethodsAndSubtypeList( 784 String enabledInputMethodsStr, 785 TextUtils.SimpleStringSplitter inputMethodSplitter, 786 TextUtils.SimpleStringSplitter subtypeSplitter) { 787 ArrayList<Pair<String, ArrayList<String>>> imsList = new ArrayList<>(); 788 if (TextUtils.isEmpty(enabledInputMethodsStr)) { 789 return imsList; 790 } 791 inputMethodSplitter.setString(enabledInputMethodsStr); 792 while (inputMethodSplitter.hasNext()) { 793 String nextImsStr = inputMethodSplitter.next(); 794 subtypeSplitter.setString(nextImsStr); 795 if (subtypeSplitter.hasNext()) { 796 ArrayList<String> subtypeHashes = new ArrayList<>(); 797 // The first element is ime id. 798 String imeId = subtypeSplitter.next(); 799 while (subtypeSplitter.hasNext()) { 800 subtypeHashes.add(subtypeSplitter.next()); 801 } 802 imsList.add(new Pair<>(imeId, subtypeHashes)); 803 } 804 } 805 return imsList; 806 } 807 InputMethodSettings(Resources res, ContentResolver resolver, ArrayMap<String, InputMethodInfo> methodMap, @UserIdInt int userId, boolean copyOnWrite)808 InputMethodSettings(Resources res, ContentResolver resolver, 809 ArrayMap<String, InputMethodInfo> methodMap, @UserIdInt int userId, 810 boolean copyOnWrite) { 811 mRes = res; 812 mResolver = resolver; 813 mMethodMap = methodMap; 814 switchCurrentUser(userId, copyOnWrite); 815 } 816 817 /** 818 * Must be called when the current user is changed. 819 * 820 * @param userId The user ID. 821 * @param copyOnWrite If {@code true}, for each settings key 822 * (e.g. {@link Settings.Secure#ACTION_INPUT_METHOD_SUBTYPE_SETTINGS}) we use the actual 823 * settings on the {@link Settings.Secure} until we do the first write operation. 824 */ switchCurrentUser(@serIdInt int userId, boolean copyOnWrite)825 void switchCurrentUser(@UserIdInt int userId, boolean copyOnWrite) { 826 if (DEBUG) { 827 Slog.d(TAG, "--- Switch the current user from " + mCurrentUserId + " to " + userId); 828 } 829 if (mCurrentUserId != userId || mCopyOnWrite != copyOnWrite) { 830 mCopyOnWriteDataStore.clear(); 831 mEnabledInputMethodsStrCache = ""; 832 // TODO: mCurrentProfileIds should be cleared here. 833 } 834 mCurrentUserId = userId; 835 mCopyOnWrite = copyOnWrite; 836 // TODO: mCurrentProfileIds should be updated here. 837 } 838 putString(@onNull String key, @Nullable String str)839 private void putString(@NonNull String key, @Nullable String str) { 840 if (mCopyOnWrite) { 841 mCopyOnWriteDataStore.put(key, str); 842 } else { 843 final int userId = CLONE_TO_MANAGED_PROFILE.contains(key) 844 ? sUserManagerInternal.getProfileParentId(mCurrentUserId) : mCurrentUserId; 845 Settings.Secure.putStringForUser(mResolver, key, str, userId); 846 } 847 } 848 849 @Nullable getString(@onNull String key, @Nullable String defaultValue)850 private String getString(@NonNull String key, @Nullable String defaultValue) { 851 final String result; 852 if (mCopyOnWrite && mCopyOnWriteDataStore.containsKey(key)) { 853 result = mCopyOnWriteDataStore.get(key); 854 } else { 855 result = Settings.Secure.getStringForUser(mResolver, key, mCurrentUserId); 856 } 857 return result != null ? result : defaultValue; 858 } 859 putInt(String key, int value)860 private void putInt(String key, int value) { 861 if (mCopyOnWrite) { 862 mCopyOnWriteDataStore.put(key, String.valueOf(value)); 863 } else { 864 final int userId = CLONE_TO_MANAGED_PROFILE.contains(key) 865 ? sUserManagerInternal.getProfileParentId(mCurrentUserId) : mCurrentUserId; 866 Settings.Secure.putIntForUser(mResolver, key, value, userId); 867 } 868 } 869 getInt(String key, int defaultValue)870 private int getInt(String key, int defaultValue) { 871 if (mCopyOnWrite && mCopyOnWriteDataStore.containsKey(key)) { 872 final String result = mCopyOnWriteDataStore.get(key); 873 return result != null ? Integer.parseInt(result) : defaultValue; 874 } 875 return Settings.Secure.getIntForUser(mResolver, key, defaultValue, mCurrentUserId); 876 } 877 putBoolean(String key, boolean value)878 private void putBoolean(String key, boolean value) { 879 putInt(key, value ? 1 : 0); 880 } 881 getBoolean(String key, boolean defaultValue)882 private boolean getBoolean(String key, boolean defaultValue) { 883 return getInt(key, defaultValue ? 1 : 0) == 1; 884 } 885 setCurrentProfileIds(int[] currentProfileIds)886 public void setCurrentProfileIds(int[] currentProfileIds) { 887 synchronized (this) { 888 mCurrentProfileIds = currentProfileIds; 889 } 890 } 891 isCurrentProfile(int userId)892 public boolean isCurrentProfile(int userId) { 893 synchronized (this) { 894 if (userId == mCurrentUserId) return true; 895 for (int i = 0; i < mCurrentProfileIds.length; i++) { 896 if (userId == mCurrentProfileIds[i]) return true; 897 } 898 return false; 899 } 900 } 901 getEnabledInputMethodListLocked()902 ArrayList<InputMethodInfo> getEnabledInputMethodListLocked() { 903 return createEnabledInputMethodListLocked( 904 getEnabledInputMethodsAndSubtypeListLocked()); 905 } 906 getEnabledInputMethodSubtypeListLocked( Context context, InputMethodInfo imi, boolean allowsImplicitlySelectedSubtypes)907 List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked( 908 Context context, InputMethodInfo imi, boolean allowsImplicitlySelectedSubtypes) { 909 List<InputMethodSubtype> enabledSubtypes = 910 getEnabledInputMethodSubtypeListLocked(imi); 911 if (allowsImplicitlySelectedSubtypes && enabledSubtypes.isEmpty()) { 912 enabledSubtypes = InputMethodUtils.getImplicitlyApplicableSubtypesLocked( 913 context.getResources(), imi); 914 } 915 return InputMethodSubtype.sort(context, 0, imi, enabledSubtypes); 916 } 917 getEnabledInputMethodSubtypeListLocked(InputMethodInfo imi)918 List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked(InputMethodInfo imi) { 919 List<Pair<String, ArrayList<String>>> imsList = 920 getEnabledInputMethodsAndSubtypeListLocked(); 921 ArrayList<InputMethodSubtype> enabledSubtypes = new ArrayList<>(); 922 if (imi != null) { 923 for (Pair<String, ArrayList<String>> imsPair : imsList) { 924 InputMethodInfo info = mMethodMap.get(imsPair.first); 925 if (info != null && info.getId().equals(imi.getId())) { 926 final int subtypeCount = info.getSubtypeCount(); 927 for (int i = 0; i < subtypeCount; ++i) { 928 InputMethodSubtype ims = info.getSubtypeAt(i); 929 for (String s: imsPair.second) { 930 if (String.valueOf(ims.hashCode()).equals(s)) { 931 enabledSubtypes.add(ims); 932 } 933 } 934 } 935 break; 936 } 937 } 938 } 939 return enabledSubtypes; 940 } 941 getEnabledInputMethodsAndSubtypeListLocked()942 List<Pair<String, ArrayList<String>>> getEnabledInputMethodsAndSubtypeListLocked() { 943 return buildInputMethodsAndSubtypeList(getEnabledInputMethodsStr(), 944 mInputMethodSplitter, 945 mSubtypeSplitter); 946 } 947 appendAndPutEnabledInputMethodLocked(String id, boolean reloadInputMethodStr)948 void appendAndPutEnabledInputMethodLocked(String id, boolean reloadInputMethodStr) { 949 if (reloadInputMethodStr) { 950 getEnabledInputMethodsStr(); 951 } 952 if (TextUtils.isEmpty(mEnabledInputMethodsStrCache)) { 953 // Add in the newly enabled input method. 954 putEnabledInputMethodsStr(id); 955 } else { 956 putEnabledInputMethodsStr( 957 mEnabledInputMethodsStrCache + INPUT_METHOD_SEPARATOR + id); 958 } 959 } 960 961 /** 962 * Build and put a string of EnabledInputMethods with removing specified Id. 963 * @return the specified id was removed or not. 964 */ buildAndPutEnabledInputMethodsStrRemovingIdLocked( StringBuilder builder, List<Pair<String, ArrayList<String>>> imsList, String id)965 boolean buildAndPutEnabledInputMethodsStrRemovingIdLocked( 966 StringBuilder builder, List<Pair<String, ArrayList<String>>> imsList, String id) { 967 boolean isRemoved = false; 968 boolean needsAppendSeparator = false; 969 for (Pair<String, ArrayList<String>> ims: imsList) { 970 String curId = ims.first; 971 if (curId.equals(id)) { 972 // We are disabling this input method, and it is 973 // currently enabled. Skip it to remove from the 974 // new list. 975 isRemoved = true; 976 } else { 977 if (needsAppendSeparator) { 978 builder.append(INPUT_METHOD_SEPARATOR); 979 } else { 980 needsAppendSeparator = true; 981 } 982 buildEnabledInputMethodsSettingString(builder, ims); 983 } 984 } 985 if (isRemoved) { 986 // Update the setting with the new list of input methods. 987 putEnabledInputMethodsStr(builder.toString()); 988 } 989 return isRemoved; 990 } 991 createEnabledInputMethodListLocked( List<Pair<String, ArrayList<String>>> imsList)992 private ArrayList<InputMethodInfo> createEnabledInputMethodListLocked( 993 List<Pair<String, ArrayList<String>>> imsList) { 994 final ArrayList<InputMethodInfo> res = new ArrayList<>(); 995 for (Pair<String, ArrayList<String>> ims: imsList) { 996 InputMethodInfo info = mMethodMap.get(ims.first); 997 if (info != null && !info.isVrOnly()) { 998 res.add(info); 999 } 1000 } 1001 return res; 1002 } 1003 putEnabledInputMethodsStr(@ullable String str)1004 void putEnabledInputMethodsStr(@Nullable String str) { 1005 if (DEBUG) { 1006 Slog.d(TAG, "putEnabledInputMethodStr: " + str); 1007 } 1008 if (TextUtils.isEmpty(str)) { 1009 // OK to coalesce to null, since getEnabledInputMethodsStr() can take care of the 1010 // empty data scenario. 1011 putString(Settings.Secure.ENABLED_INPUT_METHODS, null); 1012 } else { 1013 putString(Settings.Secure.ENABLED_INPUT_METHODS, str); 1014 } 1015 // TODO: Update callers of putEnabledInputMethodsStr to make str @NonNull. 1016 mEnabledInputMethodsStrCache = (str != null ? str : ""); 1017 } 1018 1019 @NonNull getEnabledInputMethodsStr()1020 String getEnabledInputMethodsStr() { 1021 mEnabledInputMethodsStrCache = getString(Settings.Secure.ENABLED_INPUT_METHODS, ""); 1022 if (DEBUG) { 1023 Slog.d(TAG, "getEnabledInputMethodsStr: " + mEnabledInputMethodsStrCache 1024 + ", " + mCurrentUserId); 1025 } 1026 return mEnabledInputMethodsStrCache; 1027 } 1028 saveSubtypeHistory( List<Pair<String, String>> savedImes, String newImeId, String newSubtypeId)1029 private void saveSubtypeHistory( 1030 List<Pair<String, String>> savedImes, String newImeId, String newSubtypeId) { 1031 StringBuilder builder = new StringBuilder(); 1032 boolean isImeAdded = false; 1033 if (!TextUtils.isEmpty(newImeId) && !TextUtils.isEmpty(newSubtypeId)) { 1034 builder.append(newImeId).append(INPUT_METHOD_SUBTYPE_SEPARATOR).append( 1035 newSubtypeId); 1036 isImeAdded = true; 1037 } 1038 for (Pair<String, String> ime: savedImes) { 1039 String imeId = ime.first; 1040 String subtypeId = ime.second; 1041 if (TextUtils.isEmpty(subtypeId)) { 1042 subtypeId = NOT_A_SUBTYPE_ID_STR; 1043 } 1044 if (isImeAdded) { 1045 builder.append(INPUT_METHOD_SEPARATOR); 1046 } else { 1047 isImeAdded = true; 1048 } 1049 builder.append(imeId).append(INPUT_METHOD_SUBTYPE_SEPARATOR).append( 1050 subtypeId); 1051 } 1052 // Remove the last INPUT_METHOD_SEPARATOR 1053 putSubtypeHistoryStr(builder.toString()); 1054 } 1055 addSubtypeToHistory(String imeId, String subtypeId)1056 private void addSubtypeToHistory(String imeId, String subtypeId) { 1057 List<Pair<String, String>> subtypeHistory = loadInputMethodAndSubtypeHistoryLocked(); 1058 for (Pair<String, String> ime: subtypeHistory) { 1059 if (ime.first.equals(imeId)) { 1060 if (DEBUG) { 1061 Slog.v(TAG, "Subtype found in the history: " + imeId + ", " 1062 + ime.second); 1063 } 1064 // We should break here 1065 subtypeHistory.remove(ime); 1066 break; 1067 } 1068 } 1069 if (DEBUG) { 1070 Slog.v(TAG, "Add subtype to the history: " + imeId + ", " + subtypeId); 1071 } 1072 saveSubtypeHistory(subtypeHistory, imeId, subtypeId); 1073 } 1074 putSubtypeHistoryStr(@onNull String str)1075 private void putSubtypeHistoryStr(@NonNull String str) { 1076 if (DEBUG) { 1077 Slog.d(TAG, "putSubtypeHistoryStr: " + str); 1078 } 1079 if (TextUtils.isEmpty(str)) { 1080 // OK to coalesce to null, since getSubtypeHistoryStr() can take care of the empty 1081 // data scenario. 1082 putString(Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY, null); 1083 } else { 1084 putString(Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY, str); 1085 } 1086 } 1087 getLastInputMethodAndSubtypeLocked()1088 Pair<String, String> getLastInputMethodAndSubtypeLocked() { 1089 // Gets the first one from the history 1090 return getLastSubtypeForInputMethodLockedInternal(null); 1091 } 1092 getLastSubtypeForInputMethodLocked(String imeId)1093 String getLastSubtypeForInputMethodLocked(String imeId) { 1094 Pair<String, String> ime = getLastSubtypeForInputMethodLockedInternal(imeId); 1095 if (ime != null) { 1096 return ime.second; 1097 } else { 1098 return null; 1099 } 1100 } 1101 getLastSubtypeForInputMethodLockedInternal(String imeId)1102 private Pair<String, String> getLastSubtypeForInputMethodLockedInternal(String imeId) { 1103 List<Pair<String, ArrayList<String>>> enabledImes = 1104 getEnabledInputMethodsAndSubtypeListLocked(); 1105 List<Pair<String, String>> subtypeHistory = loadInputMethodAndSubtypeHistoryLocked(); 1106 for (Pair<String, String> imeAndSubtype : subtypeHistory) { 1107 final String imeInTheHistory = imeAndSubtype.first; 1108 // If imeId is empty, returns the first IME and subtype in the history 1109 if (TextUtils.isEmpty(imeId) || imeInTheHistory.equals(imeId)) { 1110 final String subtypeInTheHistory = imeAndSubtype.second; 1111 final String subtypeHashCode = 1112 getEnabledSubtypeHashCodeForInputMethodAndSubtypeLocked( 1113 enabledImes, imeInTheHistory, subtypeInTheHistory); 1114 if (!TextUtils.isEmpty(subtypeHashCode)) { 1115 if (DEBUG) { 1116 Slog.d(TAG, "Enabled subtype found in the history: " + subtypeHashCode); 1117 } 1118 return new Pair<>(imeInTheHistory, subtypeHashCode); 1119 } 1120 } 1121 } 1122 if (DEBUG) { 1123 Slog.d(TAG, "No enabled IME found in the history"); 1124 } 1125 return null; 1126 } 1127 getEnabledSubtypeHashCodeForInputMethodAndSubtypeLocked(List<Pair<String, ArrayList<String>>> enabledImes, String imeId, String subtypeHashCode)1128 private String getEnabledSubtypeHashCodeForInputMethodAndSubtypeLocked(List<Pair<String, 1129 ArrayList<String>>> enabledImes, String imeId, String subtypeHashCode) { 1130 for (Pair<String, ArrayList<String>> enabledIme: enabledImes) { 1131 if (enabledIme.first.equals(imeId)) { 1132 final ArrayList<String> explicitlyEnabledSubtypes = enabledIme.second; 1133 final InputMethodInfo imi = mMethodMap.get(imeId); 1134 if (explicitlyEnabledSubtypes.size() == 0) { 1135 // If there are no explicitly enabled subtypes, applicable subtypes are 1136 // enabled implicitly. 1137 // If IME is enabled and no subtypes are enabled, applicable subtypes 1138 // are enabled implicitly, so needs to treat them to be enabled. 1139 if (imi != null && imi.getSubtypeCount() > 0) { 1140 List<InputMethodSubtype> implicitlySelectedSubtypes = 1141 getImplicitlyApplicableSubtypesLocked(mRes, imi); 1142 if (implicitlySelectedSubtypes != null) { 1143 final int N = implicitlySelectedSubtypes.size(); 1144 for (int i = 0; i < N; ++i) { 1145 final InputMethodSubtype st = implicitlySelectedSubtypes.get(i); 1146 if (String.valueOf(st.hashCode()).equals(subtypeHashCode)) { 1147 return subtypeHashCode; 1148 } 1149 } 1150 } 1151 } 1152 } else { 1153 for (String s: explicitlyEnabledSubtypes) { 1154 if (s.equals(subtypeHashCode)) { 1155 // If both imeId and subtypeId are enabled, return subtypeId. 1156 try { 1157 final int hashCode = Integer.parseInt(subtypeHashCode); 1158 // Check whether the subtype id is valid or not 1159 if (isValidSubtypeId(imi, hashCode)) { 1160 return s; 1161 } else { 1162 return NOT_A_SUBTYPE_ID_STR; 1163 } 1164 } catch (NumberFormatException e) { 1165 return NOT_A_SUBTYPE_ID_STR; 1166 } 1167 } 1168 } 1169 } 1170 // If imeId was enabled but subtypeId was disabled. 1171 return NOT_A_SUBTYPE_ID_STR; 1172 } 1173 } 1174 // If both imeId and subtypeId are disabled, return null 1175 return null; 1176 } 1177 loadInputMethodAndSubtypeHistoryLocked()1178 private List<Pair<String, String>> loadInputMethodAndSubtypeHistoryLocked() { 1179 ArrayList<Pair<String, String>> imsList = new ArrayList<>(); 1180 final String subtypeHistoryStr = getSubtypeHistoryStr(); 1181 if (TextUtils.isEmpty(subtypeHistoryStr)) { 1182 return imsList; 1183 } 1184 mInputMethodSplitter.setString(subtypeHistoryStr); 1185 while (mInputMethodSplitter.hasNext()) { 1186 String nextImsStr = mInputMethodSplitter.next(); 1187 mSubtypeSplitter.setString(nextImsStr); 1188 if (mSubtypeSplitter.hasNext()) { 1189 String subtypeId = NOT_A_SUBTYPE_ID_STR; 1190 // The first element is ime id. 1191 String imeId = mSubtypeSplitter.next(); 1192 while (mSubtypeSplitter.hasNext()) { 1193 subtypeId = mSubtypeSplitter.next(); 1194 break; 1195 } 1196 imsList.add(new Pair<>(imeId, subtypeId)); 1197 } 1198 } 1199 return imsList; 1200 } 1201 1202 @NonNull getSubtypeHistoryStr()1203 private String getSubtypeHistoryStr() { 1204 final String history = getString(Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY, ""); 1205 if (DEBUG) { 1206 Slog.d(TAG, "getSubtypeHistoryStr: " + history); 1207 } 1208 return history; 1209 } 1210 putSelectedInputMethod(String imeId)1211 void putSelectedInputMethod(String imeId) { 1212 if (DEBUG) { 1213 Slog.d(TAG, "putSelectedInputMethodStr: " + imeId + ", " 1214 + mCurrentUserId); 1215 } 1216 putString(Settings.Secure.DEFAULT_INPUT_METHOD, imeId); 1217 } 1218 putSelectedSubtype(int subtypeId)1219 void putSelectedSubtype(int subtypeId) { 1220 if (DEBUG) { 1221 Slog.d(TAG, "putSelectedInputMethodSubtypeStr: " + subtypeId + ", " 1222 + mCurrentUserId); 1223 } 1224 putInt(Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE, subtypeId); 1225 } 1226 1227 @Nullable getSelectedInputMethod()1228 String getSelectedInputMethod() { 1229 final String imi = getString(Settings.Secure.DEFAULT_INPUT_METHOD, null); 1230 if (DEBUG) { 1231 Slog.d(TAG, "getSelectedInputMethodStr: " + imi); 1232 } 1233 return imi; 1234 } 1235 isSubtypeSelected()1236 boolean isSubtypeSelected() { 1237 return getSelectedInputMethodSubtypeHashCode() != NOT_A_SUBTYPE_ID; 1238 } 1239 getSelectedInputMethodSubtypeHashCode()1240 private int getSelectedInputMethodSubtypeHashCode() { 1241 return getInt(Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE, NOT_A_SUBTYPE_ID); 1242 } 1243 isShowImeWithHardKeyboardEnabled()1244 boolean isShowImeWithHardKeyboardEnabled() { 1245 return getBoolean(Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD, false); 1246 } 1247 setShowImeWithHardKeyboard(boolean show)1248 void setShowImeWithHardKeyboard(boolean show) { 1249 putBoolean(Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD, show); 1250 } 1251 1252 @UserIdInt getCurrentUserId()1253 public int getCurrentUserId() { 1254 return mCurrentUserId; 1255 } 1256 getSelectedInputMethodSubtypeId(String selectedImiId)1257 int getSelectedInputMethodSubtypeId(String selectedImiId) { 1258 final InputMethodInfo imi = mMethodMap.get(selectedImiId); 1259 if (imi == null) { 1260 return NOT_A_SUBTYPE_ID; 1261 } 1262 final int subtypeHashCode = getSelectedInputMethodSubtypeHashCode(); 1263 return getSubtypeIdFromHashCode(imi, subtypeHashCode); 1264 } 1265 saveCurrentInputMethodAndSubtypeToHistory(String curMethodId, InputMethodSubtype currentSubtype)1266 void saveCurrentInputMethodAndSubtypeToHistory(String curMethodId, 1267 InputMethodSubtype currentSubtype) { 1268 String subtypeId = NOT_A_SUBTYPE_ID_STR; 1269 if (currentSubtype != null) { 1270 subtypeId = String.valueOf(currentSubtype.hashCode()); 1271 } 1272 if (canAddToLastInputMethod(currentSubtype)) { 1273 addSubtypeToHistory(curMethodId, subtypeId); 1274 } 1275 } 1276 dumpLocked(final Printer pw, final String prefix)1277 public void dumpLocked(final Printer pw, final String prefix) { 1278 pw.println(prefix + "mCurrentUserId=" + mCurrentUserId); 1279 pw.println(prefix + "mCurrentProfileIds=" + Arrays.toString(mCurrentProfileIds)); 1280 pw.println(prefix + "mCopyOnWrite=" + mCopyOnWrite); 1281 pw.println(prefix + "mEnabledInputMethodsStrCache=" + mEnabledInputMethodsStrCache); 1282 } 1283 } 1284 isSoftInputModeStateVisibleAllowed(int targetSdkVersion, @StartInputFlags int startInputFlags)1285 static boolean isSoftInputModeStateVisibleAllowed(int targetSdkVersion, 1286 @StartInputFlags int startInputFlags) { 1287 if (targetSdkVersion < Build.VERSION_CODES.P) { 1288 // for compatibility. 1289 return true; 1290 } 1291 if ((startInputFlags & StartInputFlags.VIEW_HAS_FOCUS) == 0) { 1292 return false; 1293 } 1294 if ((startInputFlags & StartInputFlags.IS_TEXT_EDITOR) == 0) { 1295 return false; 1296 } 1297 return true; 1298 } 1299 1300 /** 1301 * Converts a user ID, which can be a pseudo user ID such as {@link UserHandle#USER_ALL} to a 1302 * list of real user IDs. 1303 * 1304 * @param userIdToBeResolved A user ID. Two pseudo user ID {@link UserHandle#USER_CURRENT} and 1305 * {@link UserHandle#USER_ALL} are also supported 1306 * @param currentUserId A real user ID, which will be used when {@link UserHandle#USER_CURRENT} 1307 * is specified in {@code userIdToBeResolved}. 1308 * @param warningWriter A {@link PrintWriter} to output some debug messages. {@code null} if 1309 * no debug message is required. 1310 * @return An integer array that contain user IDs. 1311 */ resolveUserId(@serIdInt int userIdToBeResolved, @UserIdInt int currentUserId, @Nullable PrintWriter warningWriter)1312 static int[] resolveUserId(@UserIdInt int userIdToBeResolved, 1313 @UserIdInt int currentUserId, @Nullable PrintWriter warningWriter) { 1314 final UserManagerInternal userManagerInternal = 1315 LocalServices.getService(UserManagerInternal.class); 1316 1317 if (userIdToBeResolved == UserHandle.USER_ALL) { 1318 return userManagerInternal.getUserIds(); 1319 } 1320 1321 final int sourceUserId; 1322 if (userIdToBeResolved == UserHandle.USER_CURRENT) { 1323 sourceUserId = currentUserId; 1324 } else if (userIdToBeResolved < 0) { 1325 if (warningWriter != null) { 1326 warningWriter.print("Pseudo user ID "); 1327 warningWriter.print(userIdToBeResolved); 1328 warningWriter.println(" is not supported."); 1329 } 1330 return new int[]{}; 1331 } else if (userManagerInternal.exists(userIdToBeResolved)) { 1332 sourceUserId = userIdToBeResolved; 1333 } else { 1334 if (warningWriter != null) { 1335 warningWriter.print("User #"); 1336 warningWriter.print(userIdToBeResolved); 1337 warningWriter.println(" does not exit."); 1338 } 1339 return new int[]{}; 1340 } 1341 return new int[]{sourceUserId}; 1342 } 1343 } 1344