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