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