InputMethodUtils.java revision 734983fff35d9ed2b7a9848bdfbca401887d0dd8
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 HashMap<String, InputMethodSubtype> applicableModeAndSubtypesMap = 266 new HashMap<String, InputMethodSubtype>(); 267 final int N = subtypes.size(); 268 for (int i = 0; i < N; ++i) { 269 // scan overriding implicitly enabled subtypes. 270 InputMethodSubtype subtype = subtypes.get(i); 271 if (subtype.overridesImplicitlyEnabledSubtype()) { 272 final String mode = subtype.getMode(); 273 if (!applicableModeAndSubtypesMap.containsKey(mode)) { 274 applicableModeAndSubtypesMap.put(mode, subtype); 275 } 276 } 277 } 278 if (applicableModeAndSubtypesMap.size() > 0) { 279 return new ArrayList<InputMethodSubtype>(applicableModeAndSubtypesMap.values()); 280 } 281 for (int i = 0; i < N; ++i) { 282 final InputMethodSubtype subtype = subtypes.get(i); 283 final String locale = subtype.getLocale(); 284 final String mode = subtype.getMode(); 285 // When system locale starts with subtype's locale, that subtype will be applicable 286 // for system locale 287 // For instance, it's clearly applicable for cases like system locale = en_US and 288 // subtype = en, but it is not necessarily considered applicable for cases like system 289 // locale = en and subtype = en_US. 290 // We just call systemLocale.startsWith(locale) in this function because there is no 291 // need to find applicable subtypes aggressively unlike 292 // findLastResortApplicableSubtypeLocked. 293 if (systemLocale.startsWith(locale)) { 294 final InputMethodSubtype applicableSubtype = applicableModeAndSubtypesMap.get(mode); 295 // If more applicable subtypes are contained, skip. 296 if (applicableSubtype != null) { 297 if (systemLocale.equals(applicableSubtype.getLocale())) continue; 298 if (!systemLocale.equals(locale)) continue; 299 } 300 applicableModeAndSubtypesMap.put(mode, subtype); 301 } 302 } 303 final InputMethodSubtype keyboardSubtype 304 = applicableModeAndSubtypesMap.get(SUBTYPE_MODE_KEYBOARD); 305 final ArrayList<InputMethodSubtype> applicableSubtypes = new ArrayList<InputMethodSubtype>( 306 applicableModeAndSubtypesMap.values()); 307 if (keyboardSubtype != null && !keyboardSubtype.containsExtraValueKey(TAG_ASCII_CAPABLE)) { 308 for (int i = 0; i < N; ++i) { 309 final InputMethodSubtype subtype = subtypes.get(i); 310 final String mode = subtype.getMode(); 311 if (SUBTYPE_MODE_KEYBOARD.equals(mode) && subtype.containsExtraValueKey( 312 TAG_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE)) { 313 applicableSubtypes.add(subtype); 314 } 315 } 316 } 317 if (keyboardSubtype == null) { 318 InputMethodSubtype lastResortKeyboardSubtype = findLastResortApplicableSubtypeLocked( 319 res, subtypes, SUBTYPE_MODE_KEYBOARD, systemLocale, true); 320 if (lastResortKeyboardSubtype != null) { 321 applicableSubtypes.add(lastResortKeyboardSubtype); 322 } 323 } 324 return applicableSubtypes; 325 } 326 327 private static List<InputMethodSubtype> getEnabledInputMethodSubtypeList( 328 Context context, InputMethodInfo imi, List<InputMethodSubtype> enabledSubtypes, 329 boolean allowsImplicitlySelectedSubtypes) { 330 if (allowsImplicitlySelectedSubtypes && enabledSubtypes.isEmpty()) { 331 enabledSubtypes = InputMethodUtils.getImplicitlyApplicableSubtypesLocked( 332 context.getResources(), imi); 333 } 334 return InputMethodSubtype.sort(context, 0, imi, enabledSubtypes); 335 } 336 337 /** 338 * If there are no selected subtypes, tries finding the most applicable one according to the 339 * given locale. 340 * @param subtypes this function will search the most applicable subtype in subtypes 341 * @param mode subtypes will be filtered by mode 342 * @param locale subtypes will be filtered by locale 343 * @param canIgnoreLocaleAsLastResort if this function can't find the most applicable subtype, 344 * it will return the first subtype matched with mode 345 * @return the most applicable subtypeId 346 */ 347 public static InputMethodSubtype findLastResortApplicableSubtypeLocked( 348 Resources res, List<InputMethodSubtype> subtypes, String mode, String locale, 349 boolean canIgnoreLocaleAsLastResort) { 350 if (subtypes == null || subtypes.size() == 0) { 351 return null; 352 } 353 if (TextUtils.isEmpty(locale)) { 354 locale = res.getConfiguration().locale.toString(); 355 } 356 final String language = locale.substring(0, 2); 357 boolean partialMatchFound = false; 358 InputMethodSubtype applicableSubtype = null; 359 InputMethodSubtype firstMatchedModeSubtype = null; 360 final int N = subtypes.size(); 361 for (int i = 0; i < N; ++i) { 362 InputMethodSubtype subtype = subtypes.get(i); 363 final String subtypeLocale = subtype.getLocale(); 364 // An applicable subtype should match "mode". If mode is null, mode will be ignored, 365 // and all subtypes with all modes can be candidates. 366 if (mode == null || subtypes.get(i).getMode().equalsIgnoreCase(mode)) { 367 if (firstMatchedModeSubtype == null) { 368 firstMatchedModeSubtype = subtype; 369 } 370 if (locale.equals(subtypeLocale)) { 371 // Exact match (e.g. system locale is "en_US" and subtype locale is "en_US") 372 applicableSubtype = subtype; 373 break; 374 } else if (!partialMatchFound && subtypeLocale.startsWith(language)) { 375 // Partial match (e.g. system locale is "en_US" and subtype locale is "en") 376 applicableSubtype = subtype; 377 partialMatchFound = true; 378 } 379 } 380 } 381 382 if (applicableSubtype == null && canIgnoreLocaleAsLastResort) { 383 return firstMatchedModeSubtype; 384 } 385 386 // The first subtype applicable to the system locale will be defined as the most applicable 387 // subtype. 388 if (DEBUG) { 389 if (applicableSubtype != null) { 390 Slog.d(TAG, "Applicable InputMethodSubtype was found: " 391 + applicableSubtype.getMode() + "," + applicableSubtype.getLocale()); 392 } 393 } 394 return applicableSubtype; 395 } 396 397 public static boolean canAddToLastInputMethod(InputMethodSubtype subtype) { 398 if (subtype == null) return true; 399 return !subtype.isAuxiliary(); 400 } 401 402 public static void setNonSelectedSystemImesDisabledUntilUsed( 403 PackageManager packageManager, List<InputMethodInfo> enabledImis) { 404 if (DEBUG) { 405 Slog.d(TAG, "setNonSelectedSystemImesDisabledUntilUsed"); 406 } 407 final String[] systemImesDisabledUntilUsed = Resources.getSystem().getStringArray( 408 com.android.internal.R.array.config_disabledUntilUsedPreinstalledImes); 409 if (systemImesDisabledUntilUsed == null || systemImesDisabledUntilUsed.length == 0) { 410 return; 411 } 412 // Only the current spell checker should be treated as an enabled one. 413 final SpellCheckerInfo currentSpellChecker = 414 TextServicesManager.getInstance().getCurrentSpellChecker(); 415 for (final String packageName : systemImesDisabledUntilUsed) { 416 if (DEBUG) { 417 Slog.d(TAG, "check " + packageName); 418 } 419 boolean enabledIme = false; 420 for (int j = 0; j < enabledImis.size(); ++j) { 421 final InputMethodInfo imi = enabledImis.get(j); 422 if (packageName.equals(imi.getPackageName())) { 423 enabledIme = true; 424 break; 425 } 426 } 427 if (enabledIme) { 428 // enabled ime. skip 429 continue; 430 } 431 if (currentSpellChecker != null 432 && packageName.equals(currentSpellChecker.getPackageName())) { 433 // enabled spell checker. skip 434 if (DEBUG) { 435 Slog.d(TAG, packageName + " is the current spell checker. skip"); 436 } 437 continue; 438 } 439 ApplicationInfo ai = null; 440 try { 441 ai = packageManager.getApplicationInfo(packageName, 442 PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS); 443 } catch (NameNotFoundException e) { 444 Slog.w(TAG, "NameNotFoundException: " + packageName, e); 445 } 446 if (ai == null) { 447 // No app found for packageName 448 continue; 449 } 450 final boolean isSystemPackage = (ai.flags & ApplicationInfo.FLAG_SYSTEM) != 0; 451 if (!isSystemPackage) { 452 continue; 453 } 454 setDisabledUntilUsed(packageManager, packageName); 455 } 456 } 457 458 private static void setDisabledUntilUsed(PackageManager packageManager, String packageName) { 459 final int state = packageManager.getApplicationEnabledSetting(packageName); 460 if (state == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT 461 || state == PackageManager.COMPONENT_ENABLED_STATE_ENABLED) { 462 if (DEBUG) { 463 Slog.d(TAG, "Update state(" + packageName + "): DISABLED_UNTIL_USED"); 464 } 465 packageManager.setApplicationEnabledSetting(packageName, 466 PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED, 0); 467 } else { 468 if (DEBUG) { 469 Slog.d(TAG, packageName + " is already DISABLED_UNTIL_USED"); 470 } 471 } 472 } 473 474 public static CharSequence getImeAndSubtypeDisplayName(Context context, InputMethodInfo imi, 475 InputMethodSubtype subtype) { 476 final CharSequence imiLabel = imi.loadLabel(context.getPackageManager()); 477 return subtype != null 478 ? TextUtils.concat(subtype.getDisplayName(context, 479 imi.getPackageName(), imi.getServiceInfo().applicationInfo), 480 (TextUtils.isEmpty(imiLabel) ? 481 "" : " - " + imiLabel)) 482 : imiLabel; 483 } 484 485 /** 486 * Utility class for putting and getting settings for InputMethod 487 * TODO: Move all putters and getters of settings to this class. 488 */ 489 public static class InputMethodSettings { 490 // The string for enabled input method is saved as follows: 491 // example: ("ime0;subtype0;subtype1;subtype2:ime1:ime2;subtype0") 492 private static final char INPUT_METHOD_SEPARATER = ':'; 493 private static final char INPUT_METHOD_SUBTYPE_SEPARATER = ';'; 494 private final TextUtils.SimpleStringSplitter mInputMethodSplitter = 495 new TextUtils.SimpleStringSplitter(INPUT_METHOD_SEPARATER); 496 497 private final TextUtils.SimpleStringSplitter mSubtypeSplitter = 498 new TextUtils.SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATER); 499 500 private final Resources mRes; 501 private final ContentResolver mResolver; 502 private final HashMap<String, InputMethodInfo> mMethodMap; 503 private final ArrayList<InputMethodInfo> mMethodList; 504 505 private String mEnabledInputMethodsStrCache; 506 private int mCurrentUserId; 507 private int[] mRelatedUserIds = new int[0]; 508 509 private static void buildEnabledInputMethodsSettingString( 510 StringBuilder builder, Pair<String, ArrayList<String>> pair) { 511 String id = pair.first; 512 ArrayList<String> subtypes = pair.second; 513 builder.append(id); 514 // Inputmethod and subtypes are saved in the settings as follows: 515 // ime0;subtype0;subtype1:ime1;subtype0:ime2:ime3;subtype0;subtype1 516 for (String subtypeId: subtypes) { 517 builder.append(INPUT_METHOD_SUBTYPE_SEPARATER).append(subtypeId); 518 } 519 } 520 521 public InputMethodSettings( 522 Resources res, ContentResolver resolver, 523 HashMap<String, InputMethodInfo> methodMap, ArrayList<InputMethodInfo> methodList, 524 int userId) { 525 setCurrentUserId(userId); 526 mRes = res; 527 mResolver = resolver; 528 mMethodMap = methodMap; 529 mMethodList = methodList; 530 } 531 532 public void setCurrentUserId(int userId) { 533 if (DEBUG) { 534 Slog.d(TAG, "--- Swtich the current user from " + mCurrentUserId + " to " + userId); 535 } 536 // IMMS settings are kept per user, so keep track of current user 537 mCurrentUserId = userId; 538 } 539 540 public void setRelatedUserIds(int[] relatedUserIds) { 541 synchronized (this) { 542 mRelatedUserIds = relatedUserIds; 543 } 544 } 545 546 public boolean isRelatedToOrCurrentUser(int userId) { 547 synchronized (this) { 548 if (userId == mCurrentUserId) return true; 549 for (int i = 0; i < mRelatedUserIds.length; i++) { 550 if (userId == mRelatedUserIds[i]) return true; 551 } 552 return false; 553 } 554 } 555 556 public List<InputMethodInfo> getEnabledInputMethodListLocked() { 557 return createEnabledInputMethodListLocked( 558 getEnabledInputMethodsAndSubtypeListLocked()); 559 } 560 561 public List<Pair<InputMethodInfo, ArrayList<String>>> 562 getEnabledInputMethodAndSubtypeHashCodeListLocked() { 563 return createEnabledInputMethodAndSubtypeHashCodeListLocked( 564 getEnabledInputMethodsAndSubtypeListLocked()); 565 } 566 567 public List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked( 568 Context context, InputMethodInfo imi, boolean allowsImplicitlySelectedSubtypes) { 569 List<InputMethodSubtype> enabledSubtypes = 570 getEnabledInputMethodSubtypeListLocked(imi); 571 if (allowsImplicitlySelectedSubtypes && enabledSubtypes.isEmpty()) { 572 enabledSubtypes = InputMethodUtils.getImplicitlyApplicableSubtypesLocked( 573 context.getResources(), imi); 574 } 575 return InputMethodSubtype.sort(context, 0, imi, enabledSubtypes); 576 } 577 578 public List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked( 579 InputMethodInfo imi) { 580 List<Pair<String, ArrayList<String>>> imsList = 581 getEnabledInputMethodsAndSubtypeListLocked(); 582 ArrayList<InputMethodSubtype> enabledSubtypes = 583 new ArrayList<InputMethodSubtype>(); 584 if (imi != null) { 585 for (Pair<String, ArrayList<String>> imsPair : imsList) { 586 InputMethodInfo info = mMethodMap.get(imsPair.first); 587 if (info != null && info.getId().equals(imi.getId())) { 588 final int subtypeCount = info.getSubtypeCount(); 589 for (int i = 0; i < subtypeCount; ++i) { 590 InputMethodSubtype ims = info.getSubtypeAt(i); 591 for (String s: imsPair.second) { 592 if (String.valueOf(ims.hashCode()).equals(s)) { 593 enabledSubtypes.add(ims); 594 } 595 } 596 } 597 break; 598 } 599 } 600 } 601 return enabledSubtypes; 602 } 603 604 // At the initial boot, the settings for input methods are not set, 605 // so we need to enable IME in that case. 606 public void enableAllIMEsIfThereIsNoEnabledIME() { 607 if (TextUtils.isEmpty(getEnabledInputMethodsStr())) { 608 StringBuilder sb = new StringBuilder(); 609 final int N = mMethodList.size(); 610 for (int i = 0; i < N; i++) { 611 InputMethodInfo imi = mMethodList.get(i); 612 Slog.i(TAG, "Adding: " + imi.getId()); 613 if (i > 0) sb.append(':'); 614 sb.append(imi.getId()); 615 } 616 putEnabledInputMethodsStr(sb.toString()); 617 } 618 } 619 620 public List<Pair<String, ArrayList<String>>> getEnabledInputMethodsAndSubtypeListLocked() { 621 ArrayList<Pair<String, ArrayList<String>>> imsList 622 = new ArrayList<Pair<String, ArrayList<String>>>(); 623 final String enabledInputMethodsStr = getEnabledInputMethodsStr(); 624 if (TextUtils.isEmpty(enabledInputMethodsStr)) { 625 return imsList; 626 } 627 mInputMethodSplitter.setString(enabledInputMethodsStr); 628 while (mInputMethodSplitter.hasNext()) { 629 String nextImsStr = mInputMethodSplitter.next(); 630 mSubtypeSplitter.setString(nextImsStr); 631 if (mSubtypeSplitter.hasNext()) { 632 ArrayList<String> subtypeHashes = new ArrayList<String>(); 633 // The first element is ime id. 634 String imeId = mSubtypeSplitter.next(); 635 while (mSubtypeSplitter.hasNext()) { 636 subtypeHashes.add(mSubtypeSplitter.next()); 637 } 638 imsList.add(new Pair<String, ArrayList<String>>(imeId, subtypeHashes)); 639 } 640 } 641 return imsList; 642 } 643 644 public void appendAndPutEnabledInputMethodLocked(String id, boolean reloadInputMethodStr) { 645 if (reloadInputMethodStr) { 646 getEnabledInputMethodsStr(); 647 } 648 if (TextUtils.isEmpty(mEnabledInputMethodsStrCache)) { 649 // Add in the newly enabled input method. 650 putEnabledInputMethodsStr(id); 651 } else { 652 putEnabledInputMethodsStr( 653 mEnabledInputMethodsStrCache + INPUT_METHOD_SEPARATER + id); 654 } 655 } 656 657 /** 658 * Build and put a string of EnabledInputMethods with removing specified Id. 659 * @return the specified id was removed or not. 660 */ 661 public boolean buildAndPutEnabledInputMethodsStrRemovingIdLocked( 662 StringBuilder builder, List<Pair<String, ArrayList<String>>> imsList, String id) { 663 boolean isRemoved = false; 664 boolean needsAppendSeparator = false; 665 for (Pair<String, ArrayList<String>> ims: imsList) { 666 String curId = ims.first; 667 if (curId.equals(id)) { 668 // We are disabling this input method, and it is 669 // currently enabled. Skip it to remove from the 670 // new list. 671 isRemoved = true; 672 } else { 673 if (needsAppendSeparator) { 674 builder.append(INPUT_METHOD_SEPARATER); 675 } else { 676 needsAppendSeparator = true; 677 } 678 buildEnabledInputMethodsSettingString(builder, ims); 679 } 680 } 681 if (isRemoved) { 682 // Update the setting with the new list of input methods. 683 putEnabledInputMethodsStr(builder.toString()); 684 } 685 return isRemoved; 686 } 687 688 private List<InputMethodInfo> createEnabledInputMethodListLocked( 689 List<Pair<String, ArrayList<String>>> imsList) { 690 final ArrayList<InputMethodInfo> res = new ArrayList<InputMethodInfo>(); 691 for (Pair<String, ArrayList<String>> ims: imsList) { 692 InputMethodInfo info = mMethodMap.get(ims.first); 693 if (info != null) { 694 res.add(info); 695 } 696 } 697 return res; 698 } 699 700 private List<Pair<InputMethodInfo, ArrayList<String>>> 701 createEnabledInputMethodAndSubtypeHashCodeListLocked( 702 List<Pair<String, ArrayList<String>>> imsList) { 703 final ArrayList<Pair<InputMethodInfo, ArrayList<String>>> res 704 = new ArrayList<Pair<InputMethodInfo, ArrayList<String>>>(); 705 for (Pair<String, ArrayList<String>> ims : imsList) { 706 InputMethodInfo info = mMethodMap.get(ims.first); 707 if (info != null) { 708 res.add(new Pair<InputMethodInfo, ArrayList<String>>(info, ims.second)); 709 } 710 } 711 return res; 712 } 713 714 private void putEnabledInputMethodsStr(String str) { 715 Settings.Secure.putStringForUser( 716 mResolver, Settings.Secure.ENABLED_INPUT_METHODS, str, mCurrentUserId); 717 mEnabledInputMethodsStrCache = str; 718 if (DEBUG) { 719 Slog.d(TAG, "putEnabledInputMethodStr: " + str); 720 } 721 } 722 723 public String getEnabledInputMethodsStr() { 724 mEnabledInputMethodsStrCache = Settings.Secure.getStringForUser( 725 mResolver, Settings.Secure.ENABLED_INPUT_METHODS, mCurrentUserId); 726 if (DEBUG) { 727 Slog.d(TAG, "getEnabledInputMethodsStr: " + mEnabledInputMethodsStrCache 728 + ", " + mCurrentUserId); 729 } 730 return mEnabledInputMethodsStrCache; 731 } 732 733 private void saveSubtypeHistory( 734 List<Pair<String, String>> savedImes, String newImeId, String newSubtypeId) { 735 StringBuilder builder = new StringBuilder(); 736 boolean isImeAdded = false; 737 if (!TextUtils.isEmpty(newImeId) && !TextUtils.isEmpty(newSubtypeId)) { 738 builder.append(newImeId).append(INPUT_METHOD_SUBTYPE_SEPARATER).append( 739 newSubtypeId); 740 isImeAdded = true; 741 } 742 for (Pair<String, String> ime: savedImes) { 743 String imeId = ime.first; 744 String subtypeId = ime.second; 745 if (TextUtils.isEmpty(subtypeId)) { 746 subtypeId = NOT_A_SUBTYPE_ID_STR; 747 } 748 if (isImeAdded) { 749 builder.append(INPUT_METHOD_SEPARATER); 750 } else { 751 isImeAdded = true; 752 } 753 builder.append(imeId).append(INPUT_METHOD_SUBTYPE_SEPARATER).append( 754 subtypeId); 755 } 756 // Remove the last INPUT_METHOD_SEPARATER 757 putSubtypeHistoryStr(builder.toString()); 758 } 759 760 private void addSubtypeToHistory(String imeId, String subtypeId) { 761 List<Pair<String, String>> subtypeHistory = loadInputMethodAndSubtypeHistoryLocked(); 762 for (Pair<String, String> ime: subtypeHistory) { 763 if (ime.first.equals(imeId)) { 764 if (DEBUG) { 765 Slog.v(TAG, "Subtype found in the history: " + imeId + ", " 766 + ime.second); 767 } 768 // We should break here 769 subtypeHistory.remove(ime); 770 break; 771 } 772 } 773 if (DEBUG) { 774 Slog.v(TAG, "Add subtype to the history: " + imeId + ", " + subtypeId); 775 } 776 saveSubtypeHistory(subtypeHistory, imeId, subtypeId); 777 } 778 779 private void putSubtypeHistoryStr(String str) { 780 if (DEBUG) { 781 Slog.d(TAG, "putSubtypeHistoryStr: " + str); 782 } 783 Settings.Secure.putStringForUser( 784 mResolver, Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY, str, mCurrentUserId); 785 } 786 787 public Pair<String, String> getLastInputMethodAndSubtypeLocked() { 788 // Gets the first one from the history 789 return getLastSubtypeForInputMethodLockedInternal(null); 790 } 791 792 public String getLastSubtypeForInputMethodLocked(String imeId) { 793 Pair<String, String> ime = getLastSubtypeForInputMethodLockedInternal(imeId); 794 if (ime != null) { 795 return ime.second; 796 } else { 797 return null; 798 } 799 } 800 801 private Pair<String, String> getLastSubtypeForInputMethodLockedInternal(String imeId) { 802 List<Pair<String, ArrayList<String>>> enabledImes = 803 getEnabledInputMethodsAndSubtypeListLocked(); 804 List<Pair<String, String>> subtypeHistory = loadInputMethodAndSubtypeHistoryLocked(); 805 for (Pair<String, String> imeAndSubtype : subtypeHistory) { 806 final String imeInTheHistory = imeAndSubtype.first; 807 // If imeId is empty, returns the first IME and subtype in the history 808 if (TextUtils.isEmpty(imeId) || imeInTheHistory.equals(imeId)) { 809 final String subtypeInTheHistory = imeAndSubtype.second; 810 final String subtypeHashCode = 811 getEnabledSubtypeHashCodeForInputMethodAndSubtypeLocked( 812 enabledImes, imeInTheHistory, subtypeInTheHistory); 813 if (!TextUtils.isEmpty(subtypeHashCode)) { 814 if (DEBUG) { 815 Slog.d(TAG, "Enabled subtype found in the history: " + subtypeHashCode); 816 } 817 return new Pair<String, String>(imeInTheHistory, subtypeHashCode); 818 } 819 } 820 } 821 if (DEBUG) { 822 Slog.d(TAG, "No enabled IME found in the history"); 823 } 824 return null; 825 } 826 827 private String getEnabledSubtypeHashCodeForInputMethodAndSubtypeLocked(List<Pair<String, 828 ArrayList<String>>> enabledImes, String imeId, String subtypeHashCode) { 829 for (Pair<String, ArrayList<String>> enabledIme: enabledImes) { 830 if (enabledIme.first.equals(imeId)) { 831 final ArrayList<String> explicitlyEnabledSubtypes = enabledIme.second; 832 final InputMethodInfo imi = mMethodMap.get(imeId); 833 if (explicitlyEnabledSubtypes.size() == 0) { 834 // If there are no explicitly enabled subtypes, applicable subtypes are 835 // enabled implicitly. 836 // If IME is enabled and no subtypes are enabled, applicable subtypes 837 // are enabled implicitly, so needs to treat them to be enabled. 838 if (imi != null && imi.getSubtypeCount() > 0) { 839 List<InputMethodSubtype> implicitlySelectedSubtypes = 840 getImplicitlyApplicableSubtypesLocked(mRes, imi); 841 if (implicitlySelectedSubtypes != null) { 842 final int N = implicitlySelectedSubtypes.size(); 843 for (int i = 0; i < N; ++i) { 844 final InputMethodSubtype st = implicitlySelectedSubtypes.get(i); 845 if (String.valueOf(st.hashCode()).equals(subtypeHashCode)) { 846 return subtypeHashCode; 847 } 848 } 849 } 850 } 851 } else { 852 for (String s: explicitlyEnabledSubtypes) { 853 if (s.equals(subtypeHashCode)) { 854 // If both imeId and subtypeId are enabled, return subtypeId. 855 try { 856 final int hashCode = Integer.valueOf(subtypeHashCode); 857 // Check whether the subtype id is valid or not 858 if (isValidSubtypeId(imi, hashCode)) { 859 return s; 860 } else { 861 return NOT_A_SUBTYPE_ID_STR; 862 } 863 } catch (NumberFormatException e) { 864 return NOT_A_SUBTYPE_ID_STR; 865 } 866 } 867 } 868 } 869 // If imeId was enabled but subtypeId was disabled. 870 return NOT_A_SUBTYPE_ID_STR; 871 } 872 } 873 // If both imeId and subtypeId are disabled, return null 874 return null; 875 } 876 877 private List<Pair<String, String>> loadInputMethodAndSubtypeHistoryLocked() { 878 ArrayList<Pair<String, String>> imsList = new ArrayList<Pair<String, String>>(); 879 final String subtypeHistoryStr = getSubtypeHistoryStr(); 880 if (TextUtils.isEmpty(subtypeHistoryStr)) { 881 return imsList; 882 } 883 mInputMethodSplitter.setString(subtypeHistoryStr); 884 while (mInputMethodSplitter.hasNext()) { 885 String nextImsStr = mInputMethodSplitter.next(); 886 mSubtypeSplitter.setString(nextImsStr); 887 if (mSubtypeSplitter.hasNext()) { 888 String subtypeId = NOT_A_SUBTYPE_ID_STR; 889 // The first element is ime id. 890 String imeId = mSubtypeSplitter.next(); 891 while (mSubtypeSplitter.hasNext()) { 892 subtypeId = mSubtypeSplitter.next(); 893 break; 894 } 895 imsList.add(new Pair<String, String>(imeId, subtypeId)); 896 } 897 } 898 return imsList; 899 } 900 901 private String getSubtypeHistoryStr() { 902 if (DEBUG) { 903 Slog.d(TAG, "getSubtypeHistoryStr: " + Settings.Secure.getStringForUser( 904 mResolver, Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY, mCurrentUserId)); 905 } 906 return Settings.Secure.getStringForUser( 907 mResolver, Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY, mCurrentUserId); 908 } 909 910 public void putSelectedInputMethod(String imeId) { 911 if (DEBUG) { 912 Slog.d(TAG, "putSelectedInputMethodStr: " + imeId + ", " 913 + mCurrentUserId); 914 } 915 Settings.Secure.putStringForUser( 916 mResolver, Settings.Secure.DEFAULT_INPUT_METHOD, imeId, mCurrentUserId); 917 } 918 919 public void putSelectedSubtype(int subtypeId) { 920 if (DEBUG) { 921 Slog.d(TAG, "putSelectedInputMethodSubtypeStr: " + subtypeId + ", " 922 + mCurrentUserId); 923 } 924 Settings.Secure.putIntForUser(mResolver, Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE, 925 subtypeId, mCurrentUserId); 926 } 927 928 public String getDisabledSystemInputMethods() { 929 return Settings.Secure.getStringForUser( 930 mResolver, Settings.Secure.DISABLED_SYSTEM_INPUT_METHODS, mCurrentUserId); 931 } 932 933 public String getSelectedInputMethod() { 934 if (DEBUG) { 935 Slog.d(TAG, "getSelectedInputMethodStr: " + Settings.Secure.getStringForUser( 936 mResolver, Settings.Secure.DEFAULT_INPUT_METHOD, mCurrentUserId) 937 + ", " + mCurrentUserId); 938 } 939 return Settings.Secure.getStringForUser( 940 mResolver, Settings.Secure.DEFAULT_INPUT_METHOD, mCurrentUserId); 941 } 942 943 public boolean isSubtypeSelected() { 944 return getSelectedInputMethodSubtypeHashCode() != NOT_A_SUBTYPE_ID; 945 } 946 947 private int getSelectedInputMethodSubtypeHashCode() { 948 try { 949 return Settings.Secure.getIntForUser( 950 mResolver, Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE, mCurrentUserId); 951 } catch (SettingNotFoundException e) { 952 return NOT_A_SUBTYPE_ID; 953 } 954 } 955 956 public int getCurrentUserId() { 957 return mCurrentUserId; 958 } 959 960 public int getSelectedInputMethodSubtypeId(String selectedImiId) { 961 final InputMethodInfo imi = mMethodMap.get(selectedImiId); 962 if (imi == null) { 963 return NOT_A_SUBTYPE_ID; 964 } 965 final int subtypeHashCode = getSelectedInputMethodSubtypeHashCode(); 966 return getSubtypeIdFromHashCode(imi, subtypeHashCode); 967 } 968 969 public void saveCurrentInputMethodAndSubtypeToHistory( 970 String curMethodId, InputMethodSubtype currentSubtype) { 971 String subtypeId = NOT_A_SUBTYPE_ID_STR; 972 if (currentSubtype != null) { 973 subtypeId = String.valueOf(currentSubtype.hashCode()); 974 } 975 if (canAddToLastInputMethod(currentSubtype)) { 976 addSubtypeToHistory(curMethodId, subtypeId); 977 } 978 } 979 980 public HashMap<InputMethodInfo, List<InputMethodSubtype>> 981 getExplicitlyOrImplicitlyEnabledInputMethodsAndSubtypeListLocked(Context context) { 982 HashMap<InputMethodInfo, List<InputMethodSubtype>> enabledInputMethodAndSubtypes = 983 new HashMap<InputMethodInfo, List<InputMethodSubtype>>(); 984 for (InputMethodInfo imi: getEnabledInputMethodListLocked()) { 985 enabledInputMethodAndSubtypes.put( 986 imi, getEnabledInputMethodSubtypeListLocked(context, imi, true)); 987 } 988 return enabledInputMethodAndSubtypes; 989 } 990 } 991} 992