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