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