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