1 /* 2 * Copyright (C) 2013 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.internal.inputmethod; 18 19 import android.app.AppOpsManager; 20 import android.content.ContentResolver; 21 import android.content.Context; 22 import android.content.pm.ApplicationInfo; 23 import android.content.pm.PackageManager; 24 import android.content.pm.PackageManager.NameNotFoundException; 25 import android.content.res.Resources; 26 import android.provider.Settings; 27 import android.provider.Settings.SettingNotFoundException; 28 import android.text.TextUtils; 29 import android.util.Pair; 30 import android.util.Slog; 31 import android.view.inputmethod.InputMethodInfo; 32 import android.view.inputmethod.InputMethodSubtype; 33 import android.view.textservice.SpellCheckerInfo; 34 import android.view.textservice.TextServicesManager; 35 36 import java.util.ArrayList; 37 import java.util.HashMap; 38 import java.util.List; 39 import java.util.Locale; 40 41 /** 42 * InputMethodManagerUtils contains some static methods that provides IME informations. 43 * This methods are supposed to be used in both the framework and the Settings application. 44 */ 45 public class InputMethodUtils { 46 public static final boolean DEBUG = false; 47 public static final int NOT_A_SUBTYPE_ID = -1; 48 public static final String SUBTYPE_MODE_ANY = null; 49 public static final String SUBTYPE_MODE_KEYBOARD = "keyboard"; 50 public static final String SUBTYPE_MODE_VOICE = "voice"; 51 private static final String TAG = "InputMethodUtils"; 52 private static final Locale ENGLISH_LOCALE = new Locale("en"); 53 private static final String NOT_A_SUBTYPE_ID_STR = String.valueOf(NOT_A_SUBTYPE_ID); 54 private static final String TAG_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE = 55 "EnabledWhenDefaultIsNotAsciiCapable"; 56 private static final String TAG_ASCII_CAPABLE = "AsciiCapable"; 57 /** 58 * Used in {@link #getFallbackLocaleForDefaultIme(ArrayList, Context)} to find the fallback IMEs 59 * that are mainly used until the system becomes ready. Note that {@link Locale} in this array 60 * is checked with {@link Locale#equals(Object)}, which means that {@code Locale.ENGLISH} 61 * doesn't automatically match {@code Locale("en", "IN")}. 62 */ 63 private static final Locale[] SEARCH_ORDER_OF_FALLBACK_LOCALES = { 64 Locale.ENGLISH, // "en" 65 Locale.US, // "en_US" 66 Locale.UK, // "en_GB" 67 }; 68 69 private InputMethodUtils() { 70 // This utility class is not publicly instantiable. 71 } 72 73 // ---------------------------------------------------------------------- 74 // Utilities for debug 75 public static String getStackTrace() { 76 final StringBuilder sb = new StringBuilder(); 77 try { 78 throw new RuntimeException(); 79 } catch (RuntimeException e) { 80 final StackTraceElement[] frames = e.getStackTrace(); 81 // Start at 1 because the first frame is here and we don't care about it 82 for (int j = 1; j < frames.length; ++j) { 83 sb.append(frames[j].toString() + "\n"); 84 } 85 } 86 return sb.toString(); 87 } 88 89 public static String getApiCallStack() { 90 String apiCallStack = ""; 91 try { 92 throw new RuntimeException(); 93 } catch (RuntimeException e) { 94 final StackTraceElement[] frames = e.getStackTrace(); 95 for (int j = 1; j < frames.length; ++j) { 96 final String tempCallStack = frames[j].toString(); 97 if (TextUtils.isEmpty(apiCallStack)) { 98 // Overwrite apiCallStack if it's empty 99 apiCallStack = tempCallStack; 100 } else if (tempCallStack.indexOf("Transact(") < 0) { 101 // Overwrite apiCallStack if it's not a binder call 102 apiCallStack = tempCallStack; 103 } else { 104 break; 105 } 106 } 107 } 108 return apiCallStack; 109 } 110 // ---------------------------------------------------------------------- 111 112 public static boolean isSystemIme(InputMethodInfo inputMethod) { 113 return (inputMethod.getServiceInfo().applicationInfo.flags 114 & ApplicationInfo.FLAG_SYSTEM) != 0; 115 } 116 117 /** 118 * @deprecated Use {@link Locale} returned from 119 * {@link #getFallbackLocaleForDefaultIme(ArrayList)} instead. 120 */ 121 @Deprecated 122 public static boolean isSystemImeThatHasEnglishKeyboardSubtype(InputMethodInfo imi) { 123 if (!isSystemIme(imi)) { 124 return false; 125 } 126 return containsSubtypeOf(imi, ENGLISH_LOCALE.getLanguage(), SUBTYPE_MODE_KEYBOARD); 127 } 128 129 public static Locale getFallbackLocaleForDefaultIme(final ArrayList<InputMethodInfo> imis, 130 final Context context) { 131 for (final Locale fallbackLocale : SEARCH_ORDER_OF_FALLBACK_LOCALES) { 132 for (int i = 0; i < imis.size(); ++i) { 133 final InputMethodInfo imi = imis.get(i); 134 if (isSystemIme(imi) && imi.isDefault(context) && 135 containsSubtypeOf(imi, fallbackLocale, false /* ignoreCountry */, 136 SUBTYPE_MODE_KEYBOARD)) { 137 return fallbackLocale; 138 } 139 } 140 } 141 return null; 142 } 143 144 private static boolean isSystemAuxilialyImeThatHasAutomaticSubtype(InputMethodInfo imi) { 145 if (!isSystemIme(imi)) { 146 return false; 147 } 148 if (!imi.isAuxiliaryIme()) { 149 return false; 150 } 151 final int subtypeCount = imi.getSubtypeCount(); 152 for (int i = 0; i < subtypeCount; ++i) { 153 final InputMethodSubtype s = imi.getSubtypeAt(i); 154 if (s.overridesImplicitlyEnabledSubtype()) { 155 return true; 156 } 157 } 158 return false; 159 } 160 161 public static Locale getSystemLocaleFromContext(final Context context) { 162 try { 163 return context.getResources().getConfiguration().locale; 164 } catch (Resources.NotFoundException ex) { 165 return null; 166 } 167 } 168 169 public static ArrayList<InputMethodInfo> getDefaultEnabledImes( 170 Context context, boolean isSystemReady, ArrayList<InputMethodInfo> imis) { 171 // OK to store null in fallbackLocale because isImeThatHasSubtypeOf() is null-tolerant. 172 final Locale fallbackLocale = getFallbackLocaleForDefaultIme(imis, context); 173 174 if (!isSystemReady) { 175 final ArrayList<InputMethodInfo> retval = new ArrayList<>(); 176 for (int i = 0; i < imis.size(); ++i) { 177 final InputMethodInfo imi = imis.get(i); 178 // TODO: We should check isAsciiCapable instead of relying on fallbackLocale. 179 if (isSystemIme(imi) && imi.isDefault(context) && 180 isImeThatHasSubtypeOf(imi, fallbackLocale, false /* ignoreCountry */, 181 SUBTYPE_MODE_KEYBOARD)) { 182 retval.add(imi); 183 } 184 } 185 return retval; 186 } 187 188 // OK to store null in fallbackLocale because isImeThatHasSubtypeOf() is null-tolerant. 189 final Locale systemLocale = getSystemLocaleFromContext(context); 190 // TODO: Use LinkedHashSet to simplify the code. 191 final ArrayList<InputMethodInfo> retval = new ArrayList<>(); 192 boolean systemLocaleKeyboardImeFound = false; 193 194 // First, try to find IMEs with taking the system locale country into consideration. 195 for (int i = 0; i < imis.size(); ++i) { 196 final InputMethodInfo imi = imis.get(i); 197 if (!isSystemIme(imi) || !imi.isDefault(context)) { 198 continue; 199 } 200 final boolean isSystemLocaleKeyboardIme = isImeThatHasSubtypeOf(imi, systemLocale, 201 false /* ignoreCountry */, SUBTYPE_MODE_KEYBOARD); 202 // TODO: We should check isAsciiCapable instead of relying on fallbackLocale. 203 // TODO: Use LinkedHashSet to simplify the code. 204 if (isSystemLocaleKeyboardIme || 205 isImeThatHasSubtypeOf(imi, fallbackLocale, false /* ignoreCountry */, 206 SUBTYPE_MODE_ANY)) { 207 retval.add(imi); 208 } 209 systemLocaleKeyboardImeFound |= isSystemLocaleKeyboardIme; 210 } 211 212 // System locale country doesn't match any IMEs, try to find IMEs in a country-agnostic 213 // way. 214 if (!systemLocaleKeyboardImeFound) { 215 for (int i = 0; i < imis.size(); ++i) { 216 final InputMethodInfo imi = imis.get(i); 217 if (!isSystemIme(imi) || !imi.isDefault(context)) { 218 continue; 219 } 220 if (isImeThatHasSubtypeOf(imi, fallbackLocale, false /* ignoreCountry */, 221 SUBTYPE_MODE_KEYBOARD)) { 222 // IMEs that have fallback locale are already added in the previous loop. We 223 // don't need to add them again here. 224 // TODO: Use LinkedHashSet to simplify the code. 225 continue; 226 } 227 if (isImeThatHasSubtypeOf(imi, systemLocale, true /* ignoreCountry */, 228 SUBTYPE_MODE_ANY)) { 229 retval.add(imi); 230 } 231 } 232 } 233 234 // If one or more auxiliary input methods are available, OK to stop populating the list. 235 for (int i = 0; i < retval.size(); ++i) { 236 if (retval.get(i).isAuxiliaryIme()) { 237 return retval; 238 } 239 } 240 for (int i = 0; i < imis.size(); ++i) { 241 final InputMethodInfo imi = imis.get(i); 242 if (isSystemAuxilialyImeThatHasAutomaticSubtype(imi)) { 243 retval.add(imi); 244 } 245 } 246 return retval; 247 } 248 249 public static boolean isImeThatHasSubtypeOf(final InputMethodInfo imi, 250 final Locale locale, final boolean ignoreCountry, final String mode) { 251 if (locale == null) { 252 return false; 253 } 254 return containsSubtypeOf(imi, locale, ignoreCountry, mode); 255 } 256 257 /** 258 * @deprecated Use {@link #isSystemIme(InputMethodInfo)} and 259 * {@link InputMethodInfo#isDefault(Context)} and 260 * {@link #isImeThatHasSubtypeOf(InputMethodInfo, Locale, boolean, String))} instead. 261 */ 262 @Deprecated 263 public static boolean isValidSystemDefaultIme( 264 boolean isSystemReady, InputMethodInfo imi, Context context) { 265 if (!isSystemReady) { 266 return false; 267 } 268 if (!isSystemIme(imi)) { 269 return false; 270 } 271 if (imi.getIsDefaultResourceId() != 0) { 272 try { 273 if (imi.isDefault(context) && containsSubtypeOf( 274 imi, context.getResources().getConfiguration().locale.getLanguage(), 275 SUBTYPE_MODE_ANY)) { 276 return true; 277 } 278 } catch (Resources.NotFoundException ex) { 279 } 280 } 281 if (imi.getSubtypeCount() == 0) { 282 Slog.w(TAG, "Found no subtypes in a system IME: " + imi.getPackageName()); 283 } 284 return false; 285 } 286 287 public static boolean containsSubtypeOf(final InputMethodInfo imi, 288 final Locale locale, final boolean ignoreCountry, final String mode) { 289 final int N = imi.getSubtypeCount(); 290 for (int i = 0; i < N; ++i) { 291 final InputMethodSubtype subtype = imi.getSubtypeAt(i); 292 if (ignoreCountry) { 293 final Locale subtypeLocale = new Locale(getLanguageFromLocaleString( 294 subtype.getLocale())); 295 if (!subtypeLocale.getLanguage().equals(locale.getLanguage())) { 296 continue; 297 } 298 } else { 299 // TODO: Use {@link Locale#toLanguageTag()} and 300 // {@link Locale#forLanguageTag(languageTag)} instead. 301 if (!TextUtils.equals(subtype.getLocale(), locale.toString())) { 302 continue; 303 } 304 } 305 if (mode == SUBTYPE_MODE_ANY || TextUtils.isEmpty(mode) || 306 mode.equalsIgnoreCase(subtype.getMode())) { 307 return true; 308 } 309 } 310 return false; 311 } 312 313 /** 314 * @deprecated Use {@link #containsSubtypeOf(InputMethodInfo, Locale, boolean, String)} instead. 315 */ 316 @Deprecated 317 public static boolean containsSubtypeOf(InputMethodInfo imi, String language, String mode) { 318 final int N = imi.getSubtypeCount(); 319 for (int i = 0; i < N; ++i) { 320 final InputMethodSubtype subtype = imi.getSubtypeAt(i); 321 if (!subtype.getLocale().startsWith(language)) { 322 continue; 323 } 324 if (mode == SUBTYPE_MODE_ANY || TextUtils.isEmpty(mode) || 325 mode.equalsIgnoreCase(subtype.getMode())) { 326 return true; 327 } 328 } 329 return false; 330 } 331 332 public static ArrayList<InputMethodSubtype> getSubtypes(InputMethodInfo imi) { 333 ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>(); 334 final int subtypeCount = imi.getSubtypeCount(); 335 for (int i = 0; i < subtypeCount; ++i) { 336 subtypes.add(imi.getSubtypeAt(i)); 337 } 338 return subtypes; 339 } 340 341 public static ArrayList<InputMethodSubtype> getOverridingImplicitlyEnabledSubtypes( 342 InputMethodInfo imi, String mode) { 343 ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>(); 344 final int subtypeCount = imi.getSubtypeCount(); 345 for (int i = 0; i < subtypeCount; ++i) { 346 final InputMethodSubtype subtype = imi.getSubtypeAt(i); 347 if (subtype.overridesImplicitlyEnabledSubtype() && subtype.getMode().equals(mode)) { 348 subtypes.add(subtype); 349 } 350 } 351 return subtypes; 352 } 353 354 public static InputMethodInfo getMostApplicableDefaultIME(List<InputMethodInfo> enabledImes) { 355 if (enabledImes == null || enabledImes.isEmpty()) { 356 return null; 357 } 358 // We'd prefer to fall back on a system IME, since that is safer. 359 int i = enabledImes.size(); 360 int firstFoundSystemIme = -1; 361 while (i > 0) { 362 i--; 363 final InputMethodInfo imi = enabledImes.get(i); 364 if (InputMethodUtils.isSystemImeThatHasEnglishKeyboardSubtype(imi) 365 && !imi.isAuxiliaryIme()) { 366 return imi; 367 } 368 if (firstFoundSystemIme < 0 && InputMethodUtils.isSystemIme(imi) 369 && !imi.isAuxiliaryIme()) { 370 firstFoundSystemIme = i; 371 } 372 } 373 return enabledImes.get(Math.max(firstFoundSystemIme, 0)); 374 } 375 376 public static boolean isValidSubtypeId(InputMethodInfo imi, int subtypeHashCode) { 377 return getSubtypeIdFromHashCode(imi, subtypeHashCode) != NOT_A_SUBTYPE_ID; 378 } 379 380 public static int getSubtypeIdFromHashCode(InputMethodInfo imi, int subtypeHashCode) { 381 if (imi != null) { 382 final int subtypeCount = imi.getSubtypeCount(); 383 for (int i = 0; i < subtypeCount; ++i) { 384 InputMethodSubtype ims = imi.getSubtypeAt(i); 385 if (subtypeHashCode == ims.hashCode()) { 386 return i; 387 } 388 } 389 } 390 return NOT_A_SUBTYPE_ID; 391 } 392 393 private static ArrayList<InputMethodSubtype> getImplicitlyApplicableSubtypesLocked( 394 Resources res, InputMethodInfo imi) { 395 final List<InputMethodSubtype> subtypes = InputMethodUtils.getSubtypes(imi); 396 final String systemLocale = res.getConfiguration().locale.toString(); 397 if (TextUtils.isEmpty(systemLocale)) return new ArrayList<InputMethodSubtype>(); 398 final String systemLanguage = res.getConfiguration().locale.getLanguage(); 399 final HashMap<String, InputMethodSubtype> applicableModeAndSubtypesMap = 400 new HashMap<String, InputMethodSubtype>(); 401 final int N = subtypes.size(); 402 for (int i = 0; i < N; ++i) { 403 // scan overriding implicitly enabled subtypes. 404 InputMethodSubtype subtype = subtypes.get(i); 405 if (subtype.overridesImplicitlyEnabledSubtype()) { 406 final String mode = subtype.getMode(); 407 if (!applicableModeAndSubtypesMap.containsKey(mode)) { 408 applicableModeAndSubtypesMap.put(mode, subtype); 409 } 410 } 411 } 412 if (applicableModeAndSubtypesMap.size() > 0) { 413 return new ArrayList<InputMethodSubtype>(applicableModeAndSubtypesMap.values()); 414 } 415 for (int i = 0; i < N; ++i) { 416 final InputMethodSubtype subtype = subtypes.get(i); 417 final String locale = subtype.getLocale(); 418 final String mode = subtype.getMode(); 419 final String language = getLanguageFromLocaleString(locale); 420 // When system locale starts with subtype's locale, that subtype will be applicable 421 // for system locale. We need to make sure the languages are the same, to prevent 422 // locales like "fil" (Filipino) being matched by "fi" (Finnish). 423 // 424 // For instance, it's clearly applicable for cases like system locale = en_US and 425 // subtype = en, but it is not necessarily considered applicable for cases like system 426 // locale = en and subtype = en_US. 427 // 428 // We just call systemLocale.startsWith(locale) in this function because there is no 429 // need to find applicable subtypes aggressively unlike 430 // findLastResortApplicableSubtypeLocked. 431 // 432 // TODO: This check is broken. It won't take scripts into account and doesn't 433 // account for the mandatory conversions performed by Locale#toString. 434 if (language.equals(systemLanguage) && systemLocale.startsWith(locale)) { 435 final InputMethodSubtype applicableSubtype = applicableModeAndSubtypesMap.get(mode); 436 // If more applicable subtypes are contained, skip. 437 if (applicableSubtype != null) { 438 if (systemLocale.equals(applicableSubtype.getLocale())) continue; 439 if (!systemLocale.equals(locale)) continue; 440 } 441 applicableModeAndSubtypesMap.put(mode, subtype); 442 } 443 } 444 final InputMethodSubtype keyboardSubtype 445 = applicableModeAndSubtypesMap.get(SUBTYPE_MODE_KEYBOARD); 446 final ArrayList<InputMethodSubtype> applicableSubtypes = new ArrayList<InputMethodSubtype>( 447 applicableModeAndSubtypesMap.values()); 448 if (keyboardSubtype != null && !keyboardSubtype.containsExtraValueKey(TAG_ASCII_CAPABLE)) { 449 for (int i = 0; i < N; ++i) { 450 final InputMethodSubtype subtype = subtypes.get(i); 451 final String mode = subtype.getMode(); 452 if (SUBTYPE_MODE_KEYBOARD.equals(mode) && subtype.containsExtraValueKey( 453 TAG_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE)) { 454 applicableSubtypes.add(subtype); 455 } 456 } 457 } 458 if (keyboardSubtype == null) { 459 InputMethodSubtype lastResortKeyboardSubtype = findLastResortApplicableSubtypeLocked( 460 res, subtypes, SUBTYPE_MODE_KEYBOARD, systemLocale, true); 461 if (lastResortKeyboardSubtype != null) { 462 applicableSubtypes.add(lastResortKeyboardSubtype); 463 } 464 } 465 return applicableSubtypes; 466 } 467 468 private static List<InputMethodSubtype> getEnabledInputMethodSubtypeList( 469 Context context, InputMethodInfo imi, List<InputMethodSubtype> enabledSubtypes, 470 boolean allowsImplicitlySelectedSubtypes) { 471 if (allowsImplicitlySelectedSubtypes && enabledSubtypes.isEmpty()) { 472 enabledSubtypes = InputMethodUtils.getImplicitlyApplicableSubtypesLocked( 473 context.getResources(), imi); 474 } 475 return InputMethodSubtype.sort(context, 0, imi, enabledSubtypes); 476 } 477 478 /** 479 * Returns the language component of a given locale string. 480 * TODO: Use {@link Locale#toLanguageTag()} and {@link Locale#forLanguageTag(languageTag)} 481 */ 482 public static String getLanguageFromLocaleString(String locale) { 483 final int idx = locale.indexOf('_'); 484 if (idx < 0) { 485 return locale; 486 } else { 487 return locale.substring(0, idx); 488 } 489 } 490 491 /** 492 * If there are no selected subtypes, tries finding the most applicable one according to the 493 * given locale. 494 * @param subtypes this function will search the most applicable subtype in subtypes 495 * @param mode subtypes will be filtered by mode 496 * @param locale subtypes will be filtered by locale 497 * @param canIgnoreLocaleAsLastResort if this function can't find the most applicable subtype, 498 * it will return the first subtype matched with mode 499 * @return the most applicable subtypeId 500 */ 501 public static InputMethodSubtype findLastResortApplicableSubtypeLocked( 502 Resources res, List<InputMethodSubtype> subtypes, String mode, String locale, 503 boolean canIgnoreLocaleAsLastResort) { 504 if (subtypes == null || subtypes.size() == 0) { 505 return null; 506 } 507 if (TextUtils.isEmpty(locale)) { 508 locale = res.getConfiguration().locale.toString(); 509 } 510 final String language = getLanguageFromLocaleString(locale); 511 boolean partialMatchFound = false; 512 InputMethodSubtype applicableSubtype = null; 513 InputMethodSubtype firstMatchedModeSubtype = null; 514 final int N = subtypes.size(); 515 for (int i = 0; i < N; ++i) { 516 InputMethodSubtype subtype = subtypes.get(i); 517 final String subtypeLocale = subtype.getLocale(); 518 final String subtypeLanguage = getLanguageFromLocaleString(subtypeLocale); 519 // An applicable subtype should match "mode". If mode is null, mode will be ignored, 520 // and all subtypes with all modes can be candidates. 521 if (mode == null || subtypes.get(i).getMode().equalsIgnoreCase(mode)) { 522 if (firstMatchedModeSubtype == null) { 523 firstMatchedModeSubtype = subtype; 524 } 525 if (locale.equals(subtypeLocale)) { 526 // Exact match (e.g. system locale is "en_US" and subtype locale is "en_US") 527 applicableSubtype = subtype; 528 break; 529 } else if (!partialMatchFound && language.equals(subtypeLanguage)) { 530 // Partial match (e.g. system locale is "en_US" and subtype locale is "en") 531 applicableSubtype = subtype; 532 partialMatchFound = true; 533 } 534 } 535 } 536 537 if (applicableSubtype == null && canIgnoreLocaleAsLastResort) { 538 return firstMatchedModeSubtype; 539 } 540 541 // The first subtype applicable to the system locale will be defined as the most applicable 542 // subtype. 543 if (DEBUG) { 544 if (applicableSubtype != null) { 545 Slog.d(TAG, "Applicable InputMethodSubtype was found: " 546 + applicableSubtype.getMode() + "," + applicableSubtype.getLocale()); 547 } 548 } 549 return applicableSubtype; 550 } 551 552 public static boolean canAddToLastInputMethod(InputMethodSubtype subtype) { 553 if (subtype == null) return true; 554 return !subtype.isAuxiliary(); 555 } 556 557 public static void setNonSelectedSystemImesDisabledUntilUsed( 558 PackageManager packageManager, List<InputMethodInfo> enabledImis) { 559 if (DEBUG) { 560 Slog.d(TAG, "setNonSelectedSystemImesDisabledUntilUsed"); 561 } 562 final String[] systemImesDisabledUntilUsed = Resources.getSystem().getStringArray( 563 com.android.internal.R.array.config_disabledUntilUsedPreinstalledImes); 564 if (systemImesDisabledUntilUsed == null || systemImesDisabledUntilUsed.length == 0) { 565 return; 566 } 567 // Only the current spell checker should be treated as an enabled one. 568 final SpellCheckerInfo currentSpellChecker = 569 TextServicesManager.getInstance().getCurrentSpellChecker(); 570 for (final String packageName : systemImesDisabledUntilUsed) { 571 if (DEBUG) { 572 Slog.d(TAG, "check " + packageName); 573 } 574 boolean enabledIme = false; 575 for (int j = 0; j < enabledImis.size(); ++j) { 576 final InputMethodInfo imi = enabledImis.get(j); 577 if (packageName.equals(imi.getPackageName())) { 578 enabledIme = true; 579 break; 580 } 581 } 582 if (enabledIme) { 583 // enabled ime. skip 584 continue; 585 } 586 if (currentSpellChecker != null 587 && packageName.equals(currentSpellChecker.getPackageName())) { 588 // enabled spell checker. skip 589 if (DEBUG) { 590 Slog.d(TAG, packageName + " is the current spell checker. skip"); 591 } 592 continue; 593 } 594 ApplicationInfo ai = null; 595 try { 596 ai = packageManager.getApplicationInfo(packageName, 597 PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS); 598 } catch (NameNotFoundException e) { 599 Slog.w(TAG, "NameNotFoundException: " + packageName, e); 600 } 601 if (ai == null) { 602 // No app found for packageName 603 continue; 604 } 605 final boolean isSystemPackage = (ai.flags & ApplicationInfo.FLAG_SYSTEM) != 0; 606 if (!isSystemPackage) { 607 continue; 608 } 609 setDisabledUntilUsed(packageManager, packageName); 610 } 611 } 612 613 private static void setDisabledUntilUsed(PackageManager packageManager, String packageName) { 614 final int state = packageManager.getApplicationEnabledSetting(packageName); 615 if (state == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT 616 || state == PackageManager.COMPONENT_ENABLED_STATE_ENABLED) { 617 if (DEBUG) { 618 Slog.d(TAG, "Update state(" + packageName + "): DISABLED_UNTIL_USED"); 619 } 620 packageManager.setApplicationEnabledSetting(packageName, 621 PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED, 0); 622 } else { 623 if (DEBUG) { 624 Slog.d(TAG, packageName + " is already DISABLED_UNTIL_USED"); 625 } 626 } 627 } 628 629 public static CharSequence getImeAndSubtypeDisplayName(Context context, InputMethodInfo imi, 630 InputMethodSubtype subtype) { 631 final CharSequence imiLabel = imi.loadLabel(context.getPackageManager()); 632 return subtype != null 633 ? TextUtils.concat(subtype.getDisplayName(context, 634 imi.getPackageName(), imi.getServiceInfo().applicationInfo), 635 (TextUtils.isEmpty(imiLabel) ? 636 "" : " - " + imiLabel)) 637 : imiLabel; 638 } 639 640 /** 641 * Returns true if a package name belongs to a UID. 642 * 643 * <p>This is a simple wrapper of {@link AppOpsManager#checkPackage(int, String)}.</p> 644 * @param appOpsManager the {@link AppOpsManager} object to be used for the validation. 645 * @param uid the UID to be validated. 646 * @param packageName the package name. 647 * @return {@code true} if the package name belongs to the UID. 648 */ 649 public static boolean checkIfPackageBelongsToUid(final AppOpsManager appOpsManager, 650 final int uid, final String packageName) { 651 try { 652 appOpsManager.checkPackage(uid, packageName); 653 return true; 654 } catch (SecurityException e) { 655 return false; 656 } 657 } 658 659 /** 660 * Utility class for putting and getting settings for InputMethod 661 * TODO: Move all putters and getters of settings to this class. 662 */ 663 public static class InputMethodSettings { 664 // The string for enabled input method is saved as follows: 665 // example: ("ime0;subtype0;subtype1;subtype2:ime1:ime2;subtype0") 666 private static final char INPUT_METHOD_SEPARATER = ':'; 667 private static final char INPUT_METHOD_SUBTYPE_SEPARATER = ';'; 668 private final TextUtils.SimpleStringSplitter mInputMethodSplitter = 669 new TextUtils.SimpleStringSplitter(INPUT_METHOD_SEPARATER); 670 671 private final TextUtils.SimpleStringSplitter mSubtypeSplitter = 672 new TextUtils.SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATER); 673 674 private final Resources mRes; 675 private final ContentResolver mResolver; 676 private final HashMap<String, InputMethodInfo> mMethodMap; 677 private final ArrayList<InputMethodInfo> mMethodList; 678 679 private String mEnabledInputMethodsStrCache; 680 private int mCurrentUserId; 681 private int[] mCurrentProfileIds = new int[0]; 682 683 private static void buildEnabledInputMethodsSettingString( 684 StringBuilder builder, Pair<String, ArrayList<String>> pair) { 685 String id = pair.first; 686 ArrayList<String> subtypes = pair.second; 687 builder.append(id); 688 // Inputmethod and subtypes are saved in the settings as follows: 689 // ime0;subtype0;subtype1:ime1;subtype0:ime2:ime3;subtype0;subtype1 690 for (String subtypeId: subtypes) { 691 builder.append(INPUT_METHOD_SUBTYPE_SEPARATER).append(subtypeId); 692 } 693 } 694 695 public InputMethodSettings( 696 Resources res, ContentResolver resolver, 697 HashMap<String, InputMethodInfo> methodMap, ArrayList<InputMethodInfo> methodList, 698 int userId) { 699 setCurrentUserId(userId); 700 mRes = res; 701 mResolver = resolver; 702 mMethodMap = methodMap; 703 mMethodList = methodList; 704 } 705 706 public void setCurrentUserId(int userId) { 707 if (DEBUG) { 708 Slog.d(TAG, "--- Swtich the current user from " + mCurrentUserId + " to " + userId); 709 } 710 // IMMS settings are kept per user, so keep track of current user 711 mCurrentUserId = userId; 712 } 713 714 public void setCurrentProfileIds(int[] currentProfileIds) { 715 synchronized (this) { 716 mCurrentProfileIds = currentProfileIds; 717 } 718 } 719 720 public boolean isCurrentProfile(int userId) { 721 synchronized (this) { 722 if (userId == mCurrentUserId) return true; 723 for (int i = 0; i < mCurrentProfileIds.length; i++) { 724 if (userId == mCurrentProfileIds[i]) return true; 725 } 726 return false; 727 } 728 } 729 730 public List<InputMethodInfo> getEnabledInputMethodListLocked() { 731 return createEnabledInputMethodListLocked( 732 getEnabledInputMethodsAndSubtypeListLocked()); 733 } 734 735 public List<Pair<InputMethodInfo, ArrayList<String>>> 736 getEnabledInputMethodAndSubtypeHashCodeListLocked() { 737 return createEnabledInputMethodAndSubtypeHashCodeListLocked( 738 getEnabledInputMethodsAndSubtypeListLocked()); 739 } 740 741 public List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked( 742 Context context, InputMethodInfo imi, boolean allowsImplicitlySelectedSubtypes) { 743 List<InputMethodSubtype> enabledSubtypes = 744 getEnabledInputMethodSubtypeListLocked(imi); 745 if (allowsImplicitlySelectedSubtypes && enabledSubtypes.isEmpty()) { 746 enabledSubtypes = InputMethodUtils.getImplicitlyApplicableSubtypesLocked( 747 context.getResources(), imi); 748 } 749 return InputMethodSubtype.sort(context, 0, imi, enabledSubtypes); 750 } 751 752 public List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked( 753 InputMethodInfo imi) { 754 List<Pair<String, ArrayList<String>>> imsList = 755 getEnabledInputMethodsAndSubtypeListLocked(); 756 ArrayList<InputMethodSubtype> enabledSubtypes = 757 new ArrayList<InputMethodSubtype>(); 758 if (imi != null) { 759 for (Pair<String, ArrayList<String>> imsPair : imsList) { 760 InputMethodInfo info = mMethodMap.get(imsPair.first); 761 if (info != null && info.getId().equals(imi.getId())) { 762 final int subtypeCount = info.getSubtypeCount(); 763 for (int i = 0; i < subtypeCount; ++i) { 764 InputMethodSubtype ims = info.getSubtypeAt(i); 765 for (String s: imsPair.second) { 766 if (String.valueOf(ims.hashCode()).equals(s)) { 767 enabledSubtypes.add(ims); 768 } 769 } 770 } 771 break; 772 } 773 } 774 } 775 return enabledSubtypes; 776 } 777 778 // At the initial boot, the settings for input methods are not set, 779 // so we need to enable IME in that case. 780 public void enableAllIMEsIfThereIsNoEnabledIME() { 781 if (TextUtils.isEmpty(getEnabledInputMethodsStr())) { 782 StringBuilder sb = new StringBuilder(); 783 final int N = mMethodList.size(); 784 for (int i = 0; i < N; i++) { 785 InputMethodInfo imi = mMethodList.get(i); 786 Slog.i(TAG, "Adding: " + imi.getId()); 787 if (i > 0) sb.append(':'); 788 sb.append(imi.getId()); 789 } 790 putEnabledInputMethodsStr(sb.toString()); 791 } 792 } 793 794 public List<Pair<String, ArrayList<String>>> getEnabledInputMethodsAndSubtypeListLocked() { 795 ArrayList<Pair<String, ArrayList<String>>> imsList 796 = new ArrayList<Pair<String, ArrayList<String>>>(); 797 final String enabledInputMethodsStr = getEnabledInputMethodsStr(); 798 if (TextUtils.isEmpty(enabledInputMethodsStr)) { 799 return imsList; 800 } 801 mInputMethodSplitter.setString(enabledInputMethodsStr); 802 while (mInputMethodSplitter.hasNext()) { 803 String nextImsStr = mInputMethodSplitter.next(); 804 mSubtypeSplitter.setString(nextImsStr); 805 if (mSubtypeSplitter.hasNext()) { 806 ArrayList<String> subtypeHashes = new ArrayList<String>(); 807 // The first element is ime id. 808 String imeId = mSubtypeSplitter.next(); 809 while (mSubtypeSplitter.hasNext()) { 810 subtypeHashes.add(mSubtypeSplitter.next()); 811 } 812 imsList.add(new Pair<String, ArrayList<String>>(imeId, subtypeHashes)); 813 } 814 } 815 return imsList; 816 } 817 818 public void appendAndPutEnabledInputMethodLocked(String id, boolean reloadInputMethodStr) { 819 if (reloadInputMethodStr) { 820 getEnabledInputMethodsStr(); 821 } 822 if (TextUtils.isEmpty(mEnabledInputMethodsStrCache)) { 823 // Add in the newly enabled input method. 824 putEnabledInputMethodsStr(id); 825 } else { 826 putEnabledInputMethodsStr( 827 mEnabledInputMethodsStrCache + INPUT_METHOD_SEPARATER + id); 828 } 829 } 830 831 /** 832 * Build and put a string of EnabledInputMethods with removing specified Id. 833 * @return the specified id was removed or not. 834 */ 835 public boolean buildAndPutEnabledInputMethodsStrRemovingIdLocked( 836 StringBuilder builder, List<Pair<String, ArrayList<String>>> imsList, String id) { 837 boolean isRemoved = false; 838 boolean needsAppendSeparator = false; 839 for (Pair<String, ArrayList<String>> ims: imsList) { 840 String curId = ims.first; 841 if (curId.equals(id)) { 842 // We are disabling this input method, and it is 843 // currently enabled. Skip it to remove from the 844 // new list. 845 isRemoved = true; 846 } else { 847 if (needsAppendSeparator) { 848 builder.append(INPUT_METHOD_SEPARATER); 849 } else { 850 needsAppendSeparator = true; 851 } 852 buildEnabledInputMethodsSettingString(builder, ims); 853 } 854 } 855 if (isRemoved) { 856 // Update the setting with the new list of input methods. 857 putEnabledInputMethodsStr(builder.toString()); 858 } 859 return isRemoved; 860 } 861 862 private List<InputMethodInfo> createEnabledInputMethodListLocked( 863 List<Pair<String, ArrayList<String>>> imsList) { 864 final ArrayList<InputMethodInfo> res = new ArrayList<InputMethodInfo>(); 865 for (Pair<String, ArrayList<String>> ims: imsList) { 866 InputMethodInfo info = mMethodMap.get(ims.first); 867 if (info != null) { 868 res.add(info); 869 } 870 } 871 return res; 872 } 873 874 private List<Pair<InputMethodInfo, ArrayList<String>>> 875 createEnabledInputMethodAndSubtypeHashCodeListLocked( 876 List<Pair<String, ArrayList<String>>> imsList) { 877 final ArrayList<Pair<InputMethodInfo, ArrayList<String>>> res 878 = new ArrayList<Pair<InputMethodInfo, ArrayList<String>>>(); 879 for (Pair<String, ArrayList<String>> ims : imsList) { 880 InputMethodInfo info = mMethodMap.get(ims.first); 881 if (info != null) { 882 res.add(new Pair<InputMethodInfo, ArrayList<String>>(info, ims.second)); 883 } 884 } 885 return res; 886 } 887 888 private void putEnabledInputMethodsStr(String str) { 889 Settings.Secure.putStringForUser( 890 mResolver, Settings.Secure.ENABLED_INPUT_METHODS, str, mCurrentUserId); 891 mEnabledInputMethodsStrCache = str; 892 if (DEBUG) { 893 Slog.d(TAG, "putEnabledInputMethodStr: " + str); 894 } 895 } 896 897 public String getEnabledInputMethodsStr() { 898 mEnabledInputMethodsStrCache = Settings.Secure.getStringForUser( 899 mResolver, Settings.Secure.ENABLED_INPUT_METHODS, mCurrentUserId); 900 if (DEBUG) { 901 Slog.d(TAG, "getEnabledInputMethodsStr: " + mEnabledInputMethodsStrCache 902 + ", " + mCurrentUserId); 903 } 904 return mEnabledInputMethodsStrCache; 905 } 906 907 private void saveSubtypeHistory( 908 List<Pair<String, String>> savedImes, String newImeId, String newSubtypeId) { 909 StringBuilder builder = new StringBuilder(); 910 boolean isImeAdded = false; 911 if (!TextUtils.isEmpty(newImeId) && !TextUtils.isEmpty(newSubtypeId)) { 912 builder.append(newImeId).append(INPUT_METHOD_SUBTYPE_SEPARATER).append( 913 newSubtypeId); 914 isImeAdded = true; 915 } 916 for (Pair<String, String> ime: savedImes) { 917 String imeId = ime.first; 918 String subtypeId = ime.second; 919 if (TextUtils.isEmpty(subtypeId)) { 920 subtypeId = NOT_A_SUBTYPE_ID_STR; 921 } 922 if (isImeAdded) { 923 builder.append(INPUT_METHOD_SEPARATER); 924 } else { 925 isImeAdded = true; 926 } 927 builder.append(imeId).append(INPUT_METHOD_SUBTYPE_SEPARATER).append( 928 subtypeId); 929 } 930 // Remove the last INPUT_METHOD_SEPARATER 931 putSubtypeHistoryStr(builder.toString()); 932 } 933 934 private void addSubtypeToHistory(String imeId, String subtypeId) { 935 List<Pair<String, String>> subtypeHistory = loadInputMethodAndSubtypeHistoryLocked(); 936 for (Pair<String, String> ime: subtypeHistory) { 937 if (ime.first.equals(imeId)) { 938 if (DEBUG) { 939 Slog.v(TAG, "Subtype found in the history: " + imeId + ", " 940 + ime.second); 941 } 942 // We should break here 943 subtypeHistory.remove(ime); 944 break; 945 } 946 } 947 if (DEBUG) { 948 Slog.v(TAG, "Add subtype to the history: " + imeId + ", " + subtypeId); 949 } 950 saveSubtypeHistory(subtypeHistory, imeId, subtypeId); 951 } 952 953 private void putSubtypeHistoryStr(String str) { 954 if (DEBUG) { 955 Slog.d(TAG, "putSubtypeHistoryStr: " + str); 956 } 957 Settings.Secure.putStringForUser( 958 mResolver, Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY, str, mCurrentUserId); 959 } 960 961 public Pair<String, String> getLastInputMethodAndSubtypeLocked() { 962 // Gets the first one from the history 963 return getLastSubtypeForInputMethodLockedInternal(null); 964 } 965 966 public String getLastSubtypeForInputMethodLocked(String imeId) { 967 Pair<String, String> ime = getLastSubtypeForInputMethodLockedInternal(imeId); 968 if (ime != null) { 969 return ime.second; 970 } else { 971 return null; 972 } 973 } 974 975 private Pair<String, String> getLastSubtypeForInputMethodLockedInternal(String imeId) { 976 List<Pair<String, ArrayList<String>>> enabledImes = 977 getEnabledInputMethodsAndSubtypeListLocked(); 978 List<Pair<String, String>> subtypeHistory = loadInputMethodAndSubtypeHistoryLocked(); 979 for (Pair<String, String> imeAndSubtype : subtypeHistory) { 980 final String imeInTheHistory = imeAndSubtype.first; 981 // If imeId is empty, returns the first IME and subtype in the history 982 if (TextUtils.isEmpty(imeId) || imeInTheHistory.equals(imeId)) { 983 final String subtypeInTheHistory = imeAndSubtype.second; 984 final String subtypeHashCode = 985 getEnabledSubtypeHashCodeForInputMethodAndSubtypeLocked( 986 enabledImes, imeInTheHistory, subtypeInTheHistory); 987 if (!TextUtils.isEmpty(subtypeHashCode)) { 988 if (DEBUG) { 989 Slog.d(TAG, "Enabled subtype found in the history: " + subtypeHashCode); 990 } 991 return new Pair<String, String>(imeInTheHistory, subtypeHashCode); 992 } 993 } 994 } 995 if (DEBUG) { 996 Slog.d(TAG, "No enabled IME found in the history"); 997 } 998 return null; 999 } 1000 1001 private String getEnabledSubtypeHashCodeForInputMethodAndSubtypeLocked(List<Pair<String, 1002 ArrayList<String>>> enabledImes, String imeId, String subtypeHashCode) { 1003 for (Pair<String, ArrayList<String>> enabledIme: enabledImes) { 1004 if (enabledIme.first.equals(imeId)) { 1005 final ArrayList<String> explicitlyEnabledSubtypes = enabledIme.second; 1006 final InputMethodInfo imi = mMethodMap.get(imeId); 1007 if (explicitlyEnabledSubtypes.size() == 0) { 1008 // If there are no explicitly enabled subtypes, applicable subtypes are 1009 // enabled implicitly. 1010 // If IME is enabled and no subtypes are enabled, applicable subtypes 1011 // are enabled implicitly, so needs to treat them to be enabled. 1012 if (imi != null && imi.getSubtypeCount() > 0) { 1013 List<InputMethodSubtype> implicitlySelectedSubtypes = 1014 getImplicitlyApplicableSubtypesLocked(mRes, imi); 1015 if (implicitlySelectedSubtypes != null) { 1016 final int N = implicitlySelectedSubtypes.size(); 1017 for (int i = 0; i < N; ++i) { 1018 final InputMethodSubtype st = implicitlySelectedSubtypes.get(i); 1019 if (String.valueOf(st.hashCode()).equals(subtypeHashCode)) { 1020 return subtypeHashCode; 1021 } 1022 } 1023 } 1024 } 1025 } else { 1026 for (String s: explicitlyEnabledSubtypes) { 1027 if (s.equals(subtypeHashCode)) { 1028 // If both imeId and subtypeId are enabled, return subtypeId. 1029 try { 1030 final int hashCode = Integer.valueOf(subtypeHashCode); 1031 // Check whether the subtype id is valid or not 1032 if (isValidSubtypeId(imi, hashCode)) { 1033 return s; 1034 } else { 1035 return NOT_A_SUBTYPE_ID_STR; 1036 } 1037 } catch (NumberFormatException e) { 1038 return NOT_A_SUBTYPE_ID_STR; 1039 } 1040 } 1041 } 1042 } 1043 // If imeId was enabled but subtypeId was disabled. 1044 return NOT_A_SUBTYPE_ID_STR; 1045 } 1046 } 1047 // If both imeId and subtypeId are disabled, return null 1048 return null; 1049 } 1050 1051 private List<Pair<String, String>> loadInputMethodAndSubtypeHistoryLocked() { 1052 ArrayList<Pair<String, String>> imsList = new ArrayList<Pair<String, String>>(); 1053 final String subtypeHistoryStr = getSubtypeHistoryStr(); 1054 if (TextUtils.isEmpty(subtypeHistoryStr)) { 1055 return imsList; 1056 } 1057 mInputMethodSplitter.setString(subtypeHistoryStr); 1058 while (mInputMethodSplitter.hasNext()) { 1059 String nextImsStr = mInputMethodSplitter.next(); 1060 mSubtypeSplitter.setString(nextImsStr); 1061 if (mSubtypeSplitter.hasNext()) { 1062 String subtypeId = NOT_A_SUBTYPE_ID_STR; 1063 // The first element is ime id. 1064 String imeId = mSubtypeSplitter.next(); 1065 while (mSubtypeSplitter.hasNext()) { 1066 subtypeId = mSubtypeSplitter.next(); 1067 break; 1068 } 1069 imsList.add(new Pair<String, String>(imeId, subtypeId)); 1070 } 1071 } 1072 return imsList; 1073 } 1074 1075 private String getSubtypeHistoryStr() { 1076 if (DEBUG) { 1077 Slog.d(TAG, "getSubtypeHistoryStr: " + Settings.Secure.getStringForUser( 1078 mResolver, Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY, mCurrentUserId)); 1079 } 1080 return Settings.Secure.getStringForUser( 1081 mResolver, Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY, mCurrentUserId); 1082 } 1083 1084 public void putSelectedInputMethod(String imeId) { 1085 if (DEBUG) { 1086 Slog.d(TAG, "putSelectedInputMethodStr: " + imeId + ", " 1087 + mCurrentUserId); 1088 } 1089 Settings.Secure.putStringForUser( 1090 mResolver, Settings.Secure.DEFAULT_INPUT_METHOD, imeId, mCurrentUserId); 1091 } 1092 1093 public void putSelectedSubtype(int subtypeId) { 1094 if (DEBUG) { 1095 Slog.d(TAG, "putSelectedInputMethodSubtypeStr: " + subtypeId + ", " 1096 + mCurrentUserId); 1097 } 1098 Settings.Secure.putIntForUser(mResolver, Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE, 1099 subtypeId, mCurrentUserId); 1100 } 1101 1102 public String getDisabledSystemInputMethods() { 1103 return Settings.Secure.getStringForUser( 1104 mResolver, Settings.Secure.DISABLED_SYSTEM_INPUT_METHODS, mCurrentUserId); 1105 } 1106 1107 public String getSelectedInputMethod() { 1108 if (DEBUG) { 1109 Slog.d(TAG, "getSelectedInputMethodStr: " + Settings.Secure.getStringForUser( 1110 mResolver, Settings.Secure.DEFAULT_INPUT_METHOD, mCurrentUserId) 1111 + ", " + mCurrentUserId); 1112 } 1113 return Settings.Secure.getStringForUser( 1114 mResolver, Settings.Secure.DEFAULT_INPUT_METHOD, mCurrentUserId); 1115 } 1116 1117 public boolean isSubtypeSelected() { 1118 return getSelectedInputMethodSubtypeHashCode() != NOT_A_SUBTYPE_ID; 1119 } 1120 1121 private int getSelectedInputMethodSubtypeHashCode() { 1122 try { 1123 return Settings.Secure.getIntForUser( 1124 mResolver, Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE, mCurrentUserId); 1125 } catch (SettingNotFoundException e) { 1126 return NOT_A_SUBTYPE_ID; 1127 } 1128 } 1129 1130 public boolean isShowImeWithHardKeyboardEnabled() { 1131 return Settings.Secure.getIntForUser(mResolver, 1132 Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD, 0, mCurrentUserId) == 1; 1133 } 1134 1135 public void setShowImeWithHardKeyboard(boolean show) { 1136 Settings.Secure.putIntForUser(mResolver, Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD, 1137 show ? 1 : 0, mCurrentUserId); 1138 } 1139 1140 public int getCurrentUserId() { 1141 return mCurrentUserId; 1142 } 1143 1144 public int getSelectedInputMethodSubtypeId(String selectedImiId) { 1145 final InputMethodInfo imi = mMethodMap.get(selectedImiId); 1146 if (imi == null) { 1147 return NOT_A_SUBTYPE_ID; 1148 } 1149 final int subtypeHashCode = getSelectedInputMethodSubtypeHashCode(); 1150 return getSubtypeIdFromHashCode(imi, subtypeHashCode); 1151 } 1152 1153 public void saveCurrentInputMethodAndSubtypeToHistory( 1154 String curMethodId, InputMethodSubtype currentSubtype) { 1155 String subtypeId = NOT_A_SUBTYPE_ID_STR; 1156 if (currentSubtype != null) { 1157 subtypeId = String.valueOf(currentSubtype.hashCode()); 1158 } 1159 if (canAddToLastInputMethod(currentSubtype)) { 1160 addSubtypeToHistory(curMethodId, subtypeId); 1161 } 1162 } 1163 1164 public HashMap<InputMethodInfo, List<InputMethodSubtype>> 1165 getExplicitlyOrImplicitlyEnabledInputMethodsAndSubtypeListLocked(Context context) { 1166 HashMap<InputMethodInfo, List<InputMethodSubtype>> enabledInputMethodAndSubtypes = 1167 new HashMap<InputMethodInfo, List<InputMethodSubtype>>(); 1168 for (InputMethodInfo imi: getEnabledInputMethodListLocked()) { 1169 enabledInputMethodAndSubtypes.put( 1170 imi, getEnabledInputMethodSubtypeListLocked(context, imi, true)); 1171 } 1172 return enabledInputMethodAndSubtypes; 1173 } 1174 } 1175 } 1176