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