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