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