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