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 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 */ setIsAuxiliary(boolean isAuxiliary)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 */ setOverridesImplicitlyEnabledSubtype( boolean overridesImplicitlyEnabledSubtype)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 */ setIsAsciiCapable(boolean isAsciiCapable)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 */ setSubtypeIconResId(int subtypeIconResId)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 */ setSubtypeNameResId(int subtypeNameResId)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 */ setSubtypeId(int subtypeId)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 */ setSubtypeLocale(String subtypeLocale)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 */ setLanguageTag(String languageTag)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 */ setSubtypeMode(String subtypeMode)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 */ setSubtypeExtraValue(String subtypeExtraValue)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 */ build()214 public InputMethodSubtype build() { 215 return new InputMethodSubtype(this); 216 } 217 } 218 getBuilder(int nameId, int iconId, String locale, String mode, String extraValue, boolean isAuxiliary, boolean overridesImplicitlyEnabledSubtype, int id, boolean isAsciiCapable)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 InputMethodSubtype(int nameId, int iconId, String locale, String mode, String extraValue, boolean isAuxiliary, boolean overridesImplicitlyEnabledSubtype)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 InputMethodSubtype(int nameId, int iconId, String locale, String mode, String extraValue, boolean isAuxiliary, boolean overridesImplicitlyEnabledSubtype, int id)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 */ InputMethodSubtype(InputMethodSubtypeBuilder builder)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 InputMethodSubtype(Parcel source)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 */ getNameResId()334 public int getNameResId() { 335 return mSubtypeNameResId; 336 } 337 338 /** 339 * @return Resource ID of the subtype icon drawable. 340 */ getIconResId()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 getLocale()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 getLanguageTag()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 getLocaleObject()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 = SubtypeLocaleUtils.constructLocaleFromString(mSubtypeLocale); 388 } 389 return mCachedLocaleObj; 390 } 391 } 392 393 /** 394 * @return The mode of the subtype. 395 */ getMode()396 public String getMode() { 397 return mSubtypeMode; 398 } 399 400 /** 401 * @return The extra value of the subtype. 402 */ getExtraValue()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 */ isAuxiliary()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 */ overridesImplicitlyEnabledSubtype()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 */ isAsciiCapable()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 getDisplayName( Context context, String packageName, ApplicationInfo appInfo)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 getLocaleFromContext(@ullable final Context context)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 getLocaleDisplayName( @ullable Locale displayLocale, @Nullable Locale localeToDisplay, final DisplayContext displayContext)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 getExtraValueHashMap()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 */ containsExtraValueKey(String key)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 */ getExtraValueOf(String key)574 public String getExtraValueOf(String key) { 575 return getExtraValueHashMap().get(key); 576 } 577 578 @Override hashCode()579 public int hashCode() { 580 return mSubtypeHashCode; 581 } 582 583 /** 584 * @hide 585 * @return {@code true} if a valid subtype ID exists. 586 */ hasSubtypeId()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 */ getSubtypeId()595 public final int getSubtypeId() { 596 return mSubtypeId; 597 } 598 599 @Override equals(Object o)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 describeContents()620 public int describeContents() { 621 return 0; 622 } 623 624 @Override writeToParcel(Parcel dest, int parcelableFlags)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 @android.annotation.NonNull 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 hashCodeInternal(String locale, String mode, String extraValue, boolean isAuxiliary, boolean overridesImplicitlyEnabledSubtype, boolean isAsciiCapable)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 */ sort(Context context, int flags, InputMethodInfo imi, List<InputMethodSubtype> subtypeList)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 }