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