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