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