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