1 /* 2 * Copyright (C) 2010 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 android.view.inputmethod; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.content.Context; 22 import android.content.pm.ApplicationInfo; 23 import android.content.res.Configuration; 24 import android.icu.text.DisplayContext; 25 import android.icu.text.LocaleDisplayNames; 26 import android.os.Parcel; 27 import android.os.Parcelable; 28 import android.text.TextUtils; 29 import android.util.Slog; 30 31 import com.android.internal.inputmethod.InputMethodUtils; 32 33 import java.util.ArrayList; 34 import java.util.Arrays; 35 import java.util.HashMap; 36 import java.util.HashSet; 37 import java.util.IllegalFormatException; 38 import java.util.List; 39 import java.util.Locale; 40 41 /** 42 * This class is used to specify meta information of a subtype contained in an input method editor 43 * (IME). Subtype can describe locale (e.g. en_US, fr_FR...) and mode (e.g. voice, keyboard...), 44 * and is used for IME switch and settings. The input method subtype allows the system to bring up 45 * the specified subtype of the designated IME directly. 46 * 47 * <p>It should be defined in an XML resource file of the input method with the 48 * <code><subtype></code> element, which resides within an {@code <input-method>} element. 49 * For more information, see the guide to 50 * <a href="{@docRoot}guide/topics/text/creating-input-method.html"> 51 * Creating an Input Method</a>.</p> 52 * 53 * @see InputMethodInfo 54 * 55 * @attr ref android.R.styleable#InputMethod_Subtype_label 56 * @attr ref android.R.styleable#InputMethod_Subtype_icon 57 * @attr ref android.R.styleable#InputMethod_Subtype_languageTag 58 * @attr ref android.R.styleable#InputMethod_Subtype_imeSubtypeLocale 59 * @attr ref android.R.styleable#InputMethod_Subtype_imeSubtypeMode 60 * @attr ref android.R.styleable#InputMethod_Subtype_imeSubtypeExtraValue 61 * @attr ref android.R.styleable#InputMethod_Subtype_isAuxiliary 62 * @attr ref android.R.styleable#InputMethod_Subtype_overridesImplicitlyEnabledSubtype 63 * @attr ref android.R.styleable#InputMethod_Subtype_subtypeId 64 * @attr ref android.R.styleable#InputMethod_Subtype_isAsciiCapable 65 */ 66 public final class InputMethodSubtype implements Parcelable { 67 private static final String TAG = InputMethodSubtype.class.getSimpleName(); 68 private static final String LANGUAGE_TAG_NONE = ""; 69 private static final String EXTRA_VALUE_PAIR_SEPARATOR = ","; 70 private static final String EXTRA_VALUE_KEY_VALUE_SEPARATOR = "="; 71 // TODO: remove this 72 private static final String EXTRA_KEY_UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME = 73 "UntranslatableReplacementStringInSubtypeName"; 74 private static final int SUBTYPE_ID_NONE = 0; 75 76 private final boolean mIsAuxiliary; 77 private final boolean mOverridesImplicitlyEnabledSubtype; 78 private final boolean mIsAsciiCapable; 79 private final int mSubtypeHashCode; 80 private final int mSubtypeIconResId; 81 private final int mSubtypeNameResId; 82 private final int mSubtypeId; 83 private final String mSubtypeLocale; 84 private final String mSubtypeLanguageTag; 85 private final String mSubtypeMode; 86 private final String mSubtypeExtraValue; 87 private volatile HashMap<String, String> mExtraValueHashMapCache; 88 89 /** 90 * InputMethodSubtypeBuilder is a builder class of InputMethodSubtype. 91 * This class is designed to be used with 92 * {@link android.view.inputmethod.InputMethodManager#setAdditionalInputMethodSubtypes}. 93 * The developer needs to be aware of what each parameter means. 94 */ 95 public static class InputMethodSubtypeBuilder { 96 /** 97 * @param isAuxiliary should true when this subtype is auxiliary, false otherwise. 98 * An auxiliary subtype has the following differences with a regular subtype: 99 * - An auxiliary subtype cannot be chosen as the default IME in Settings. 100 * - The framework will never switch to this subtype through 101 * {@link android.view.inputmethod.InputMethodManager#switchToLastInputMethod}. 102 * Note that the subtype will still be available in the IME switcher. 103 * The intent is to allow for IMEs to specify they are meant to be invoked temporarily 104 * in a one-shot way, and to return to the previous IME once finished (e.g. voice input). 105 */ 106 public InputMethodSubtypeBuilder setIsAuxiliary(boolean isAuxiliary) { 107 mIsAuxiliary = isAuxiliary; 108 return this; 109 } 110 private boolean mIsAuxiliary = false; 111 112 /** 113 * @param overridesImplicitlyEnabledSubtype should be true if this subtype should be 114 * enabled by default if no other subtypes in the IME are enabled explicitly. Note that a 115 * subtype with this parameter set will not be shown in the list of subtypes in each IME's 116 * subtype enabler. A canonical use of this would be for an IME to supply an "automatic" 117 * subtype that adapts to the current system language. 118 */ 119 public InputMethodSubtypeBuilder setOverridesImplicitlyEnabledSubtype( 120 boolean overridesImplicitlyEnabledSubtype) { 121 mOverridesImplicitlyEnabledSubtype = overridesImplicitlyEnabledSubtype; 122 return this; 123 } 124 private boolean mOverridesImplicitlyEnabledSubtype = false; 125 126 /** 127 * @param isAsciiCapable should be true if this subtype is ASCII capable. If the subtype 128 * is ASCII capable, it should guarantee that the user can input ASCII characters with 129 * this subtype. This is important because many password fields only allow 130 * ASCII-characters. 131 */ 132 public InputMethodSubtypeBuilder setIsAsciiCapable(boolean isAsciiCapable) { 133 mIsAsciiCapable = isAsciiCapable; 134 return this; 135 } 136 private boolean mIsAsciiCapable = false; 137 138 /** 139 * @param subtypeIconResId is a resource ID of the subtype icon drawable. 140 */ 141 public InputMethodSubtypeBuilder setSubtypeIconResId(int subtypeIconResId) { 142 mSubtypeIconResId = subtypeIconResId; 143 return this; 144 } 145 private int mSubtypeIconResId = 0; 146 147 /** 148 * @param subtypeNameResId is the resource ID of the subtype name string. 149 * The string resource may have exactly one %s in it. If present, 150 * the %s part will be replaced with the locale's display name by 151 * the formatter. Please refer to {@link #getDisplayName} for details. 152 */ 153 public InputMethodSubtypeBuilder setSubtypeNameResId(int subtypeNameResId) { 154 mSubtypeNameResId = subtypeNameResId; 155 return this; 156 } 157 private int mSubtypeNameResId = 0; 158 159 /** 160 * @param subtypeId is the unique ID for this subtype. The input method framework keeps 161 * track of enabled subtypes by ID. When the IME package gets upgraded, enabled IDs will 162 * stay enabled even if other attributes are different. If the ID is unspecified or 0, 163 * Arrays.hashCode(new Object[] {locale, mode, extraValue, 164 * isAuxiliary, overridesImplicitlyEnabledSubtype, isAsciiCapable}) will be used instead. 165 */ 166 public InputMethodSubtypeBuilder setSubtypeId(int subtypeId) { 167 mSubtypeId = subtypeId; 168 return this; 169 } 170 private int mSubtypeId = SUBTYPE_ID_NONE; 171 172 /** 173 * @param subtypeLocale is the locale supported by this subtype. 174 */ 175 public InputMethodSubtypeBuilder setSubtypeLocale(String subtypeLocale) { 176 mSubtypeLocale = subtypeLocale == null ? "" : subtypeLocale; 177 return this; 178 } 179 private String mSubtypeLocale = ""; 180 181 /** 182 * @param languageTag is the BCP-47 Language Tag supported by this subtype. 183 */ 184 public InputMethodSubtypeBuilder setLanguageTag(String languageTag) { 185 mSubtypeLanguageTag = languageTag == null ? LANGUAGE_TAG_NONE : languageTag; 186 return this; 187 } 188 private String mSubtypeLanguageTag = LANGUAGE_TAG_NONE; 189 190 /** 191 * @param subtypeMode is the mode supported by this subtype. 192 */ 193 public InputMethodSubtypeBuilder setSubtypeMode(String subtypeMode) { 194 mSubtypeMode = subtypeMode == null ? "" : subtypeMode; 195 return this; 196 } 197 private String mSubtypeMode = ""; 198 /** 199 * @param subtypeExtraValue is the extra value of the subtype. This string is free-form, 200 * but the API supplies tools to deal with a key-value comma-separated list; see 201 * {@link #containsExtraValueKey} and {@link #getExtraValueOf}. 202 */ 203 public InputMethodSubtypeBuilder setSubtypeExtraValue(String subtypeExtraValue) { 204 mSubtypeExtraValue = subtypeExtraValue == null ? "" : subtypeExtraValue; 205 return this; 206 } 207 private String mSubtypeExtraValue = ""; 208 209 /** 210 * @return InputMethodSubtype using parameters in this InputMethodSubtypeBuilder. 211 */ 212 public InputMethodSubtype build() { 213 return new InputMethodSubtype(this); 214 } 215 } 216 217 private static InputMethodSubtypeBuilder getBuilder(int nameId, int iconId, String locale, 218 String mode, String extraValue, boolean isAuxiliary, 219 boolean overridesImplicitlyEnabledSubtype, int id, boolean isAsciiCapable) { 220 final InputMethodSubtypeBuilder builder = new InputMethodSubtypeBuilder(); 221 builder.mSubtypeNameResId = nameId; 222 builder.mSubtypeIconResId = iconId; 223 builder.mSubtypeLocale = locale; 224 builder.mSubtypeMode = mode; 225 builder.mSubtypeExtraValue = extraValue; 226 builder.mIsAuxiliary = isAuxiliary; 227 builder.mOverridesImplicitlyEnabledSubtype = overridesImplicitlyEnabledSubtype; 228 builder.mSubtypeId = id; 229 builder.mIsAsciiCapable = isAsciiCapable; 230 return builder; 231 } 232 233 /** 234 * Constructor with no subtype ID specified. 235 * @deprecated use {@link InputMethodSubtypeBuilder} instead. 236 * Arguments for this constructor have the same meanings as 237 * {@link InputMethodSubtype#InputMethodSubtype(int, int, String, String, String, boolean, 238 * boolean, int)} except "id". 239 */ 240 public InputMethodSubtype(int nameId, int iconId, String locale, String mode, String extraValue, 241 boolean isAuxiliary, boolean overridesImplicitlyEnabledSubtype) { 242 this(nameId, iconId, locale, mode, extraValue, isAuxiliary, 243 overridesImplicitlyEnabledSubtype, 0); 244 } 245 246 /** 247 * Constructor. 248 * @deprecated use {@link InputMethodSubtypeBuilder} instead. 249 * "isAsciiCapable" is "false" in this constructor. 250 * @param nameId Resource ID of the subtype name string. The string resource may have exactly 251 * one %s in it. If there is, the %s part will be replaced with the locale's display name by 252 * the formatter. Please refer to {@link #getDisplayName} for details. 253 * @param iconId Resource ID of the subtype icon drawable. 254 * @param locale The locale supported by the subtype 255 * @param mode The mode supported by the subtype 256 * @param extraValue The extra value of the subtype. This string is free-form, but the API 257 * supplies tools to deal with a key-value comma-separated list; see 258 * {@link #containsExtraValueKey} and {@link #getExtraValueOf}. 259 * @param isAuxiliary true when this subtype is auxiliary, false otherwise. An auxiliary 260 * subtype will not be shown in the list of enabled IMEs for choosing the current IME in 261 * the Settings even when this subtype is enabled. Please note that this subtype will still 262 * be shown in the list of IMEs in the IME switcher to allow the user to tentatively switch 263 * to this subtype while an IME is shown. The framework will never switch the current IME to 264 * this subtype by {@link android.view.inputmethod.InputMethodManager#switchToLastInputMethod}. 265 * The intent of having this flag is to allow for IMEs that are invoked in a one-shot way as 266 * auxiliary input mode, and return to the previous IME once it is finished (e.g. voice input). 267 * @param overridesImplicitlyEnabledSubtype true when this subtype should be enabled by default 268 * if no other subtypes in the IME are enabled explicitly. Note that a subtype with this 269 * parameter being true will not be shown in the list of subtypes in each IME's subtype enabler. 270 * Having an "automatic" subtype is an example use of this flag. 271 * @param id The unique ID for the subtype. The input method framework keeps track of enabled 272 * subtypes by ID. When the IME package gets upgraded, enabled IDs will stay enabled even if 273 * other attributes are different. If the ID is unspecified or 0, 274 * Arrays.hashCode(new Object[] {locale, mode, extraValue, 275 * isAuxiliary, overridesImplicitlyEnabledSubtype, isAsciiCapable}) will be used instead. 276 */ 277 public InputMethodSubtype(int nameId, int iconId, String locale, String mode, String extraValue, 278 boolean isAuxiliary, boolean overridesImplicitlyEnabledSubtype, int id) { 279 this(getBuilder(nameId, iconId, locale, mode, extraValue, isAuxiliary, 280 overridesImplicitlyEnabledSubtype, id, false)); 281 } 282 283 /** 284 * Constructor. 285 * @param builder Builder for InputMethodSubtype 286 */ 287 private InputMethodSubtype(InputMethodSubtypeBuilder builder) { 288 mSubtypeNameResId = builder.mSubtypeNameResId; 289 mSubtypeIconResId = builder.mSubtypeIconResId; 290 mSubtypeLocale = builder.mSubtypeLocale; 291 mSubtypeLanguageTag = builder.mSubtypeLanguageTag; 292 mSubtypeMode = builder.mSubtypeMode; 293 mSubtypeExtraValue = builder.mSubtypeExtraValue; 294 mIsAuxiliary = builder.mIsAuxiliary; 295 mOverridesImplicitlyEnabledSubtype = builder.mOverridesImplicitlyEnabledSubtype; 296 mSubtypeId = builder.mSubtypeId; 297 mIsAsciiCapable = builder.mIsAsciiCapable; 298 // If hashCode() of this subtype is 0 and you want to specify it as an id of this subtype, 299 // just specify 0 as this subtype's id. Then, this subtype's id is treated as 0. 300 if (mSubtypeId != SUBTYPE_ID_NONE) { 301 mSubtypeHashCode = mSubtypeId; 302 } else { 303 mSubtypeHashCode = hashCodeInternal(mSubtypeLocale, mSubtypeMode, mSubtypeExtraValue, 304 mIsAuxiliary, mOverridesImplicitlyEnabledSubtype, mIsAsciiCapable); 305 } 306 } 307 308 InputMethodSubtype(Parcel source) { 309 String s; 310 mSubtypeNameResId = source.readInt(); 311 mSubtypeIconResId = source.readInt(); 312 s = source.readString(); 313 mSubtypeLocale = s != null ? s : ""; 314 s = source.readString(); 315 mSubtypeLanguageTag = s != null ? s : LANGUAGE_TAG_NONE; 316 s = source.readString(); 317 mSubtypeMode = s != null ? s : ""; 318 s = source.readString(); 319 mSubtypeExtraValue = s != null ? s : ""; 320 mIsAuxiliary = (source.readInt() == 1); 321 mOverridesImplicitlyEnabledSubtype = (source.readInt() == 1); 322 mSubtypeHashCode = source.readInt(); 323 mSubtypeId = source.readInt(); 324 mIsAsciiCapable = (source.readInt() == 1); 325 } 326 327 /** 328 * @return Resource ID of the subtype name string. 329 */ 330 public int getNameResId() { 331 return mSubtypeNameResId; 332 } 333 334 /** 335 * @return Resource ID of the subtype icon drawable. 336 */ 337 public int getIconResId() { 338 return mSubtypeIconResId; 339 } 340 341 /** 342 * @return The locale of the subtype. This method returns the "locale" string parameter passed 343 * to the constructor. 344 * 345 * @deprecated Use {@link #getLanguageTag()} instead. 346 */ 347 @Deprecated 348 @NonNull 349 public String getLocale() { 350 return mSubtypeLocale; 351 } 352 353 /** 354 * @return the BCP-47 Language Tag of the subtype. Returns an empty string when no Language Tag 355 * is specified. 356 * 357 * @see Locale#forLanguageTag(String) 358 */ 359 @NonNull 360 public String getLanguageTag() { 361 return mSubtypeLanguageTag; 362 } 363 364 /** 365 * @return {@link Locale} constructed from {@link #getLanguageTag()}. If the Language Tag is not 366 * specified, then try to construct from {@link #getLocale()} 367 * 368 * <p>TODO: Consider to make this a public API, or move this to support lib.</p> 369 * @hide 370 */ 371 @Nullable 372 public Locale getLocaleObject() { 373 if (!TextUtils.isEmpty(mSubtypeLanguageTag)) { 374 return Locale.forLanguageTag(mSubtypeLanguageTag); 375 } 376 return InputMethodUtils.constructLocaleFromString(mSubtypeLocale); 377 } 378 379 /** 380 * @return The mode of the subtype. 381 */ 382 public String getMode() { 383 return mSubtypeMode; 384 } 385 386 /** 387 * @return The extra value of the subtype. 388 */ 389 public String getExtraValue() { 390 return mSubtypeExtraValue; 391 } 392 393 /** 394 * @return true if this subtype is auxiliary, false otherwise. An auxiliary subtype will not be 395 * shown in the list of enabled IMEs for choosing the current IME in the Settings even when this 396 * subtype is enabled. Please note that this subtype will still be shown in the list of IMEs in 397 * the IME switcher to allow the user to tentatively switch to this subtype while an IME is 398 * shown. The framework will never switch the current IME to this subtype by 399 * {@link android.view.inputmethod.InputMethodManager#switchToLastInputMethod}. 400 * The intent of having this flag is to allow for IMEs that are invoked in a one-shot way as 401 * auxiliary input mode, and return to the previous IME once it is finished (e.g. voice input). 402 */ 403 public boolean isAuxiliary() { 404 return mIsAuxiliary; 405 } 406 407 /** 408 * @return true when this subtype will be enabled by default if no other subtypes in the IME 409 * are enabled explicitly, false otherwise. Note that a subtype with this method returning true 410 * will not be shown in the list of subtypes in each IME's subtype enabler. Having an 411 * "automatic" subtype is an example use of this flag. 412 */ 413 public boolean overridesImplicitlyEnabledSubtype() { 414 return mOverridesImplicitlyEnabledSubtype; 415 } 416 417 /** 418 * @return true if this subtype is Ascii capable, false otherwise. If the subtype is ASCII 419 * capable, it should guarantee that the user can input ASCII characters with this subtype. 420 * This is important because many password fields only allow ASCII-characters. 421 */ 422 public boolean isAsciiCapable() { 423 return mIsAsciiCapable; 424 } 425 426 /** 427 * Returns a display name for this subtype. 428 * 429 * <p>If {@code subtypeNameResId} is specified (!= 0) text generated from that resource will 430 * be returned. The localized string resource of the label should be capitalized for inclusion 431 * in UI lists. The string resource may contain at most one {@code %s}. If present, the 432 * {@code %s} will be replaced with the display name of the subtype locale in the user's locale. 433 * 434 * <p>If {@code subtypeNameResId} is not specified (== 0) the framework returns the display name 435 * of the subtype locale, as capitalized for use in UI lists, in the user's locale. 436 * 437 * @param context {@link Context} will be used for getting {@link Locale} and 438 * {@link android.content.pm.PackageManager}. 439 * @param packageName The package name of the input method. 440 * @param appInfo The {@link ApplicationInfo} of the input method. 441 * @return a display name for this subtype. 442 */ 443 @NonNull 444 public CharSequence getDisplayName( 445 Context context, String packageName, ApplicationInfo appInfo) { 446 if (mSubtypeNameResId == 0) { 447 return getLocaleDisplayName(getLocaleFromContext(context), getLocaleObject(), 448 DisplayContext.CAPITALIZATION_FOR_UI_LIST_OR_MENU); 449 } 450 451 final CharSequence subtypeName = context.getPackageManager().getText( 452 packageName, mSubtypeNameResId, appInfo); 453 if (TextUtils.isEmpty(subtypeName)) { 454 return ""; 455 } 456 final String subtypeNameString = subtypeName.toString(); 457 String replacementString; 458 if (containsExtraValueKey(EXTRA_KEY_UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME)) { 459 replacementString = getExtraValueOf( 460 EXTRA_KEY_UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME); 461 } else { 462 final DisplayContext displayContext; 463 if (TextUtils.equals(subtypeNameString, "%s")) { 464 displayContext = DisplayContext.CAPITALIZATION_FOR_UI_LIST_OR_MENU; 465 } else if (subtypeNameString.startsWith("%s")) { 466 displayContext = DisplayContext.CAPITALIZATION_FOR_BEGINNING_OF_SENTENCE; 467 } else { 468 displayContext = DisplayContext.CAPITALIZATION_FOR_MIDDLE_OF_SENTENCE; 469 } 470 replacementString = getLocaleDisplayName(getLocaleFromContext(context), 471 getLocaleObject(), displayContext); 472 } 473 if (replacementString == null) { 474 replacementString = ""; 475 } 476 try { 477 return String.format(subtypeNameString, replacementString); 478 } catch (IllegalFormatException e) { 479 Slog.w(TAG, "Found illegal format in subtype name("+ subtypeName + "): " + e); 480 return ""; 481 } 482 } 483 484 @Nullable 485 private static Locale getLocaleFromContext(@Nullable final Context context) { 486 if (context == null) { 487 return null; 488 } 489 if (context.getResources() == null) { 490 return null; 491 } 492 final Configuration configuration = context.getResources().getConfiguration(); 493 if (configuration == null) { 494 return null; 495 } 496 return configuration.getLocales().get(0); 497 } 498 499 /** 500 * @param displayLocale {@link Locale} to be used to display {@code localeToDisplay} 501 * @param localeToDisplay {@link Locale} to be displayed in {@code displayLocale} 502 * @param displayContext context parameter to be used to display {@code localeToDisplay} in 503 * {@code displayLocale} 504 * @return Returns the name of the {@code localeToDisplay} in the user's current locale. 505 */ 506 @NonNull 507 private static String getLocaleDisplayName( 508 @Nullable Locale displayLocale, @Nullable Locale localeToDisplay, 509 final DisplayContext displayContext) { 510 if (localeToDisplay == null) { 511 return ""; 512 } 513 final Locale nonNullDisplayLocale = 514 displayLocale != null ? displayLocale : Locale.getDefault(); 515 return LocaleDisplayNames 516 .getInstance(nonNullDisplayLocale, displayContext) 517 .localeDisplayName(localeToDisplay); 518 } 519 520 private HashMap<String, String> getExtraValueHashMap() { 521 if (mExtraValueHashMapCache == null) { 522 synchronized(this) { 523 if (mExtraValueHashMapCache == null) { 524 mExtraValueHashMapCache = new HashMap<String, String>(); 525 final String[] pairs = mSubtypeExtraValue.split(EXTRA_VALUE_PAIR_SEPARATOR); 526 final int N = pairs.length; 527 for (int i = 0; i < N; ++i) { 528 final String[] pair = pairs[i].split(EXTRA_VALUE_KEY_VALUE_SEPARATOR); 529 if (pair.length == 1) { 530 mExtraValueHashMapCache.put(pair[0], null); 531 } else if (pair.length > 1) { 532 if (pair.length > 2) { 533 Slog.w(TAG, "ExtraValue has two or more '='s"); 534 } 535 mExtraValueHashMapCache.put(pair[0], pair[1]); 536 } 537 } 538 } 539 } 540 } 541 return mExtraValueHashMapCache; 542 } 543 544 /** 545 * The string of ExtraValue in subtype should be defined as follows: 546 * example: key0,key1=value1,key2,key3,key4=value4 547 * @param key The key of extra value 548 * @return The subtype contains specified the extra value 549 */ 550 public boolean containsExtraValueKey(String key) { 551 return getExtraValueHashMap().containsKey(key); 552 } 553 554 /** 555 * The string of ExtraValue in subtype should be defined as follows: 556 * example: key0,key1=value1,key2,key3,key4=value4 557 * @param key The key of extra value 558 * @return The value of the specified key 559 */ 560 public String getExtraValueOf(String key) { 561 return getExtraValueHashMap().get(key); 562 } 563 564 @Override 565 public int hashCode() { 566 return mSubtypeHashCode; 567 } 568 569 /** 570 * @hide 571 * @return {@code true} if a valid subtype ID exists. 572 */ 573 public final boolean hasSubtypeId() { 574 return mSubtypeId != SUBTYPE_ID_NONE; 575 } 576 577 /** 578 * @hide 579 * @return subtype ID. {@code 0} means that not subtype ID is specified. 580 */ 581 public final int getSubtypeId() { 582 return mSubtypeId; 583 } 584 585 @Override 586 public boolean equals(Object o) { 587 if (o instanceof InputMethodSubtype) { 588 InputMethodSubtype subtype = (InputMethodSubtype) o; 589 if (subtype.mSubtypeId != 0 || mSubtypeId != 0) { 590 return (subtype.hashCode() == hashCode()); 591 } 592 return (subtype.hashCode() == hashCode()) 593 && (subtype.getLocale().equals(getLocale())) 594 && (subtype.getLanguageTag().equals(getLanguageTag())) 595 && (subtype.getMode().equals(getMode())) 596 && (subtype.getExtraValue().equals(getExtraValue())) 597 && (subtype.isAuxiliary() == isAuxiliary()) 598 && (subtype.overridesImplicitlyEnabledSubtype() 599 == overridesImplicitlyEnabledSubtype()) 600 && (subtype.isAsciiCapable() == isAsciiCapable()); 601 } 602 return false; 603 } 604 605 @Override 606 public int describeContents() { 607 return 0; 608 } 609 610 @Override 611 public void writeToParcel(Parcel dest, int parcelableFlags) { 612 dest.writeInt(mSubtypeNameResId); 613 dest.writeInt(mSubtypeIconResId); 614 dest.writeString(mSubtypeLocale); 615 dest.writeString(mSubtypeLanguageTag); 616 dest.writeString(mSubtypeMode); 617 dest.writeString(mSubtypeExtraValue); 618 dest.writeInt(mIsAuxiliary ? 1 : 0); 619 dest.writeInt(mOverridesImplicitlyEnabledSubtype ? 1 : 0); 620 dest.writeInt(mSubtypeHashCode); 621 dest.writeInt(mSubtypeId); 622 dest.writeInt(mIsAsciiCapable ? 1 : 0); 623 } 624 625 public static final Parcelable.Creator<InputMethodSubtype> CREATOR 626 = new Parcelable.Creator<InputMethodSubtype>() { 627 @Override 628 public InputMethodSubtype createFromParcel(Parcel source) { 629 return new InputMethodSubtype(source); 630 } 631 632 @Override 633 public InputMethodSubtype[] newArray(int size) { 634 return new InputMethodSubtype[size]; 635 } 636 }; 637 638 private static int hashCodeInternal(String locale, String mode, String extraValue, 639 boolean isAuxiliary, boolean overridesImplicitlyEnabledSubtype, 640 boolean isAsciiCapable) { 641 // CAVEAT: Must revisit how to compute needsToCalculateCompatibleHashCode when a new 642 // attribute is added in order to avoid enabled subtypes being unexpectedly disabled. 643 final boolean needsToCalculateCompatibleHashCode = !isAsciiCapable; 644 if (needsToCalculateCompatibleHashCode) { 645 return Arrays.hashCode(new Object[] {locale, mode, extraValue, isAuxiliary, 646 overridesImplicitlyEnabledSubtype}); 647 } 648 return Arrays.hashCode(new Object[] {locale, mode, extraValue, isAuxiliary, 649 overridesImplicitlyEnabledSubtype, isAsciiCapable}); 650 } 651 652 /** 653 * Sort the list of InputMethodSubtype 654 * @param context Context will be used for getting localized strings from IME 655 * @param flags Flags for the sort order 656 * @param imi InputMethodInfo of which subtypes are subject to be sorted 657 * @param subtypeList List of InputMethodSubtype which will be sorted 658 * @return Sorted list of subtypes 659 * @hide 660 */ 661 public static List<InputMethodSubtype> sort(Context context, int flags, InputMethodInfo imi, 662 List<InputMethodSubtype> subtypeList) { 663 if (imi == null) return subtypeList; 664 final HashSet<InputMethodSubtype> inputSubtypesSet = new HashSet<InputMethodSubtype>( 665 subtypeList); 666 final ArrayList<InputMethodSubtype> sortedList = new ArrayList<InputMethodSubtype>(); 667 int N = imi.getSubtypeCount(); 668 for (int i = 0; i < N; ++i) { 669 InputMethodSubtype subtype = imi.getSubtypeAt(i); 670 if (inputSubtypesSet.contains(subtype)) { 671 sortedList.add(subtype); 672 inputSubtypesSet.remove(subtype); 673 } 674 } 675 // If subtypes in inputSubtypesSet remain, that means these subtypes are not 676 // contained in imi, so the remaining subtypes will be appended. 677 for (InputMethodSubtype subtype: inputSubtypesSet) { 678 sortedList.add(subtype); 679 } 680 return sortedList; 681 } 682 } 683