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