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     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          */
setIsAuxiliary(boolean isAuxiliary)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          */
setOverridesImplicitlyEnabledSubtype( boolean overridesImplicitlyEnabledSubtype)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          */
setIsAsciiCapable(boolean isAsciiCapable)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          */
setSubtypeIconResId(int subtypeIconResId)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          */
setSubtypeNameResId(int subtypeNameResId)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          */
setSubtypeId(int subtypeId)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          */
setSubtypeLocale(String subtypeLocale)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          */
setLanguageTag(String languageTag)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          */
setSubtypeMode(String subtypeMode)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          */
setSubtypeExtraValue(String subtypeExtraValue)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          */
build()214         public InputMethodSubtype build() {
215             return new InputMethodSubtype(this);
216         }
217      }
218 
getBuilder(int nameId, int iconId, String locale, String mode, String extraValue, boolean isAuxiliary, boolean overridesImplicitlyEnabledSubtype, int id, boolean isAsciiCapable)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
InputMethodSubtype(int nameId, int iconId, String locale, String mode, String extraValue, boolean isAuxiliary, boolean overridesImplicitlyEnabledSubtype)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
InputMethodSubtype(int nameId, int iconId, String locale, String mode, String extraValue, boolean isAuxiliary, boolean overridesImplicitlyEnabledSubtype, int id)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      */
InputMethodSubtype(InputMethodSubtypeBuilder builder)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 
InputMethodSubtype(Parcel source)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      */
getNameResId()334     public int getNameResId() {
335         return mSubtypeNameResId;
336     }
337 
338     /**
339      * @return Resource ID of the subtype icon drawable.
340      */
getIconResId()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
getLocale()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
getLanguageTag()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
getLocaleObject()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 = SubtypeLocaleUtils.constructLocaleFromString(mSubtypeLocale);
388             }
389             return mCachedLocaleObj;
390         }
391     }
392 
393     /**
394      * @return The mode of the subtype.
395      */
getMode()396     public String getMode() {
397         return mSubtypeMode;
398     }
399 
400     /**
401      * @return The extra value of the subtype.
402      */
getExtraValue()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      */
isAuxiliary()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      */
overridesImplicitlyEnabledSubtype()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      */
isAsciiCapable()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
getDisplayName( Context context, String packageName, ApplicationInfo appInfo)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
getLocaleFromContext(@ullable final Context context)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
getLocaleDisplayName( @ullable Locale displayLocale, @Nullable Locale localeToDisplay, final DisplayContext displayContext)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 
getExtraValueHashMap()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      */
containsExtraValueKey(String key)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      */
getExtraValueOf(String key)574     public String getExtraValueOf(String key) {
575         return getExtraValueHashMap().get(key);
576     }
577 
578     @Override
hashCode()579     public int hashCode() {
580         return mSubtypeHashCode;
581     }
582 
583     /**
584      * @hide
585      * @return {@code true} if a valid subtype ID exists.
586      */
hasSubtypeId()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      */
getSubtypeId()595     public final int getSubtypeId() {
596         return mSubtypeId;
597     }
598 
599     @Override
equals(Object o)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
describeContents()620     public int describeContents() {
621         return 0;
622     }
623 
624     @Override
writeToParcel(Parcel dest, int parcelableFlags)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 @android.annotation.NonNull 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 
hashCodeInternal(String locale, String mode, String extraValue, boolean isAuxiliary, boolean overridesImplicitlyEnabledSubtype, boolean isAsciiCapable)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      */
sort(Context context, int flags, InputMethodInfo imi, List<InputMethodSubtype> subtypeList)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 }