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
17package android.view.inputmethod;
18
19import android.annotation.NonNull;
20import android.annotation.Nullable;
21import android.content.Context;
22import android.content.pm.ApplicationInfo;
23import android.content.res.Configuration;
24import android.icu.text.DisplayContext;
25import android.icu.text.LocaleDisplayNames;
26import android.os.Parcel;
27import android.os.Parcelable;
28import android.text.TextUtils;
29import android.util.Slog;
30
31import com.android.internal.inputmethod.InputMethodUtils;
32
33import java.util.ArrayList;
34import java.util.Arrays;
35import java.util.HashMap;
36import java.util.HashSet;
37import java.util.IllegalFormatException;
38import java.util.List;
39import 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 */
66public 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         */
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         */
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         */
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         */
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         */
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         */
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         */
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         */
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         */
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         */
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         */
212        public InputMethodSubtype build() {
213            return new InputMethodSubtype(this);
214        }
215     }
216
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    public InputMethodSubtype(int nameId, int iconId, String locale, String mode, String extraValue,
241            boolean isAuxiliary, boolean overridesImplicitlyEnabledSubtype) {
242        this(nameId, iconId, locale, mode, extraValue, isAuxiliary,
243                overridesImplicitlyEnabledSubtype, 0);
244    }
245
246    /**
247     * Constructor.
248     * @deprecated use {@link InputMethodSubtypeBuilder} instead.
249     * "isAsciiCapable" is "false" in this constructor.
250     * @param nameId Resource ID of the subtype name string. The string resource may have exactly
251     * one %s in it. If there is, the %s part will be replaced with the locale's display name by
252     * the formatter. Please refer to {@link #getDisplayName} for details.
253     * @param iconId Resource ID of the subtype icon drawable.
254     * @param locale The locale supported by the subtype
255     * @param mode The mode supported by the subtype
256     * @param extraValue The extra value of the subtype. This string is free-form, but the API
257     * supplies tools to deal with a key-value comma-separated list; see
258     * {@link #containsExtraValueKey} and {@link #getExtraValueOf}.
259     * @param isAuxiliary true when this subtype is auxiliary, false otherwise. An auxiliary
260     * subtype will not be shown in the list of enabled IMEs for choosing the current IME in
261     * the Settings even when this subtype is enabled. Please note that this subtype will still
262     * be shown in the list of IMEs in the IME switcher to allow the user to tentatively switch
263     * to this subtype while an IME is shown. The framework will never switch the current IME to
264     * this subtype by {@link android.view.inputmethod.InputMethodManager#switchToLastInputMethod}.
265     * The intent of having this flag is to allow for IMEs that are invoked in a one-shot way as
266     * auxiliary input mode, and return to the previous IME once it is finished (e.g. voice input).
267     * @param overridesImplicitlyEnabledSubtype true when this subtype should be enabled by default
268     * if no other subtypes in the IME are enabled explicitly. Note that a subtype with this
269     * parameter being true will not be shown in the list of subtypes in each IME's subtype enabler.
270     * Having an "automatic" subtype is an example use of this flag.
271     * @param id The unique ID for the subtype. The input method framework keeps track of enabled
272     * subtypes by ID. When the IME package gets upgraded, enabled IDs will stay enabled even if
273     * other attributes are different. If the ID is unspecified or 0,
274     * Arrays.hashCode(new Object[] {locale, mode, extraValue,
275     * isAuxiliary, overridesImplicitlyEnabledSubtype, isAsciiCapable}) will be used instead.
276     */
277    public InputMethodSubtype(int nameId, int iconId, String locale, String mode, String extraValue,
278            boolean isAuxiliary, boolean overridesImplicitlyEnabledSubtype, int id) {
279        this(getBuilder(nameId, iconId, locale, mode, extraValue, isAuxiliary,
280                overridesImplicitlyEnabledSubtype, id, false));
281    }
282
283    /**
284     * Constructor.
285     * @param builder Builder for InputMethodSubtype
286     */
287    private InputMethodSubtype(InputMethodSubtypeBuilder builder) {
288        mSubtypeNameResId = builder.mSubtypeNameResId;
289        mSubtypeIconResId = builder.mSubtypeIconResId;
290        mSubtypeLocale = builder.mSubtypeLocale;
291        mSubtypeLanguageTag = builder.mSubtypeLanguageTag;
292        mSubtypeMode = builder.mSubtypeMode;
293        mSubtypeExtraValue = builder.mSubtypeExtraValue;
294        mIsAuxiliary = builder.mIsAuxiliary;
295        mOverridesImplicitlyEnabledSubtype = builder.mOverridesImplicitlyEnabledSubtype;
296        mSubtypeId = builder.mSubtypeId;
297        mIsAsciiCapable = builder.mIsAsciiCapable;
298        // If hashCode() of this subtype is 0 and you want to specify it as an id of this subtype,
299        // just specify 0 as this subtype's id. Then, this subtype's id is treated as 0.
300        if (mSubtypeId != SUBTYPE_ID_NONE) {
301            mSubtypeHashCode = mSubtypeId;
302        } else {
303            mSubtypeHashCode = hashCodeInternal(mSubtypeLocale, mSubtypeMode, mSubtypeExtraValue,
304                    mIsAuxiliary, mOverridesImplicitlyEnabledSubtype, mIsAsciiCapable);
305        }
306    }
307
308    InputMethodSubtype(Parcel source) {
309        String s;
310        mSubtypeNameResId = source.readInt();
311        mSubtypeIconResId = source.readInt();
312        s = source.readString();
313        mSubtypeLocale = s != null ? s : "";
314        s = source.readString();
315        mSubtypeLanguageTag = s != null ? s : LANGUAGE_TAG_NONE;
316        s = source.readString();
317        mSubtypeMode = s != null ? s : "";
318        s = source.readString();
319        mSubtypeExtraValue = s != null ? s : "";
320        mIsAuxiliary = (source.readInt() == 1);
321        mOverridesImplicitlyEnabledSubtype = (source.readInt() == 1);
322        mSubtypeHashCode = source.readInt();
323        mSubtypeId = source.readInt();
324        mIsAsciiCapable = (source.readInt() == 1);
325    }
326
327    /**
328     * @return Resource ID of the subtype name string.
329     */
330    public int getNameResId() {
331        return mSubtypeNameResId;
332    }
333
334    /**
335     * @return Resource ID of the subtype icon drawable.
336     */
337    public int getIconResId() {
338        return mSubtypeIconResId;
339    }
340
341    /**
342     * @return The locale of the subtype. This method returns the "locale" string parameter passed
343     * to the constructor.
344     *
345     * @deprecated Use {@link #getLanguageTag()} instead.
346     */
347    @Deprecated
348    @NonNull
349    public String getLocale() {
350        return mSubtypeLocale;
351    }
352
353    /**
354     * @return the BCP-47 Language Tag of the subtype.  Returns an empty string when no Language Tag
355     * is specified.
356     *
357     * @see Locale#forLanguageTag(String)
358     */
359    @NonNull
360    public String getLanguageTag() {
361        return mSubtypeLanguageTag;
362    }
363
364    /**
365     * @return {@link Locale} constructed from {@link #getLanguageTag()}. If the Language Tag is not
366     * specified, then try to construct from {@link #getLocale()}
367     *
368     * <p>TODO: Consider to make this a public API, or move this to support lib.</p>
369     * @hide
370     */
371    @Nullable
372    public Locale getLocaleObject() {
373        if (!TextUtils.isEmpty(mSubtypeLanguageTag)) {
374            return Locale.forLanguageTag(mSubtypeLanguageTag);
375        }
376        return InputMethodUtils.constructLocaleFromString(mSubtypeLocale);
377    }
378
379    /**
380     * @return The mode of the subtype.
381     */
382    public String getMode() {
383        return mSubtypeMode;
384    }
385
386    /**
387     * @return The extra value of the subtype.
388     */
389    public String getExtraValue() {
390        return mSubtypeExtraValue;
391    }
392
393    /**
394     * @return true if this subtype is auxiliary, false otherwise. An auxiliary subtype will not be
395     * shown in the list of enabled IMEs for choosing the current IME in the Settings even when this
396     * subtype is enabled. Please note that this subtype will still be shown in the list of IMEs in
397     * the IME switcher to allow the user to tentatively switch to this subtype while an IME is
398     * shown. The framework will never switch the current IME to this subtype by
399     * {@link android.view.inputmethod.InputMethodManager#switchToLastInputMethod}.
400     * The intent of having this flag is to allow for IMEs that are invoked in a one-shot way as
401     * auxiliary input mode, and return to the previous IME once it is finished (e.g. voice input).
402     */
403    public boolean isAuxiliary() {
404        return mIsAuxiliary;
405    }
406
407    /**
408     * @return true when this subtype will be enabled by default if no other subtypes in the IME
409     * are enabled explicitly, false otherwise. Note that a subtype with this method returning true
410     * will not be shown in the list of subtypes in each IME's subtype enabler. Having an
411     * "automatic" subtype is an example use of this flag.
412     */
413    public boolean overridesImplicitlyEnabledSubtype() {
414        return mOverridesImplicitlyEnabledSubtype;
415    }
416
417    /**
418     * @return true if this subtype is Ascii capable, false otherwise. If the subtype is ASCII
419     * capable, it should guarantee that the user can input ASCII characters with this subtype.
420     * This is important because many password fields only allow ASCII-characters.
421     */
422    public boolean isAsciiCapable() {
423        return mIsAsciiCapable;
424    }
425
426    /**
427     * Returns a display name for this subtype.
428     *
429     * <p>If {@code subtypeNameResId} is specified (!= 0) text generated from that resource will
430     * be returned. The localized string resource of the label should be capitalized for inclusion
431     * in UI lists. The string resource may contain at most one {@code %s}. If present, the
432     * {@code %s} will be replaced with the display name of the subtype locale in the user's locale.
433     *
434     * <p>If {@code subtypeNameResId} is not specified (== 0) the framework returns the display name
435     * of the subtype locale, as capitalized for use in UI lists, in the user's locale.
436     *
437     * @param context {@link Context} will be used for getting {@link Locale} and
438     * {@link android.content.pm.PackageManager}.
439     * @param packageName The package name of the input method.
440     * @param appInfo The {@link ApplicationInfo} of the input method.
441     * @return a display name for this subtype.
442     */
443    @NonNull
444    public CharSequence getDisplayName(
445            Context context, String packageName, ApplicationInfo appInfo) {
446        if (mSubtypeNameResId == 0) {
447            return getLocaleDisplayName(getLocaleFromContext(context), getLocaleObject(),
448                    DisplayContext.CAPITALIZATION_FOR_UI_LIST_OR_MENU);
449        }
450
451        final CharSequence subtypeName = context.getPackageManager().getText(
452                packageName, mSubtypeNameResId, appInfo);
453        if (TextUtils.isEmpty(subtypeName)) {
454            return "";
455        }
456        final String subtypeNameString = subtypeName.toString();
457        String replacementString;
458        if (containsExtraValueKey(EXTRA_KEY_UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME)) {
459            replacementString = getExtraValueOf(
460                    EXTRA_KEY_UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME);
461        } else {
462            final DisplayContext displayContext;
463            if (TextUtils.equals(subtypeNameString, "%s")) {
464                displayContext = DisplayContext.CAPITALIZATION_FOR_UI_LIST_OR_MENU;
465            } else if (subtypeNameString.startsWith("%s")) {
466                displayContext = DisplayContext.CAPITALIZATION_FOR_BEGINNING_OF_SENTENCE;
467            } else {
468                displayContext = DisplayContext.CAPITALIZATION_FOR_MIDDLE_OF_SENTENCE;
469            }
470            replacementString = getLocaleDisplayName(getLocaleFromContext(context),
471                    getLocaleObject(), displayContext);
472        }
473        if (replacementString == null) {
474            replacementString = "";
475        }
476        try {
477            return String.format(subtypeNameString, replacementString);
478        } catch (IllegalFormatException e) {
479            Slog.w(TAG, "Found illegal format in subtype name("+ subtypeName + "): " + e);
480            return "";
481        }
482    }
483
484    @Nullable
485    private static Locale getLocaleFromContext(@Nullable final Context context) {
486        if (context == null) {
487            return null;
488        }
489        if (context.getResources() == null) {
490            return null;
491        }
492        final Configuration configuration = context.getResources().getConfiguration();
493        if (configuration == null) {
494            return null;
495        }
496        return configuration.getLocales().get(0);
497    }
498
499    /**
500     * @param displayLocale {@link Locale} to be used to display {@code localeToDisplay}
501     * @param localeToDisplay {@link Locale} to be displayed in {@code displayLocale}
502     * @param displayContext context parameter to be used to display {@code localeToDisplay} in
503     * {@code displayLocale}
504     * @return Returns the name of the {@code localeToDisplay} in the user's current locale.
505     */
506    @NonNull
507    private static String getLocaleDisplayName(
508            @Nullable Locale displayLocale, @Nullable Locale localeToDisplay,
509            final DisplayContext displayContext) {
510        if (localeToDisplay == null) {
511            return "";
512        }
513        final Locale nonNullDisplayLocale =
514                displayLocale != null ? displayLocale : Locale.getDefault();
515        return LocaleDisplayNames
516                .getInstance(nonNullDisplayLocale, displayContext)
517                .localeDisplayName(localeToDisplay);
518    }
519
520    private HashMap<String, String> getExtraValueHashMap() {
521        if (mExtraValueHashMapCache == null) {
522            synchronized(this) {
523                if (mExtraValueHashMapCache == null) {
524                    mExtraValueHashMapCache = new HashMap<String, String>();
525                    final String[] pairs = mSubtypeExtraValue.split(EXTRA_VALUE_PAIR_SEPARATOR);
526                    final int N = pairs.length;
527                    for (int i = 0; i < N; ++i) {
528                        final String[] pair = pairs[i].split(EXTRA_VALUE_KEY_VALUE_SEPARATOR);
529                        if (pair.length == 1) {
530                            mExtraValueHashMapCache.put(pair[0], null);
531                        } else if (pair.length > 1) {
532                            if (pair.length > 2) {
533                                Slog.w(TAG, "ExtraValue has two or more '='s");
534                            }
535                            mExtraValueHashMapCache.put(pair[0], pair[1]);
536                        }
537                    }
538                }
539            }
540        }
541        return mExtraValueHashMapCache;
542    }
543
544    /**
545     * The string of ExtraValue in subtype should be defined as follows:
546     * example: key0,key1=value1,key2,key3,key4=value4
547     * @param key The key of extra value
548     * @return The subtype contains specified the extra value
549     */
550    public boolean containsExtraValueKey(String key) {
551        return getExtraValueHashMap().containsKey(key);
552    }
553
554    /**
555     * The string of ExtraValue in subtype should be defined as follows:
556     * example: key0,key1=value1,key2,key3,key4=value4
557     * @param key The key of extra value
558     * @return The value of the specified key
559     */
560    public String getExtraValueOf(String key) {
561        return getExtraValueHashMap().get(key);
562    }
563
564    @Override
565    public int hashCode() {
566        return mSubtypeHashCode;
567    }
568
569    /**
570     * @hide
571     * @return {@code true} if a valid subtype ID exists.
572     */
573    public final boolean hasSubtypeId() {
574        return mSubtypeId != SUBTYPE_ID_NONE;
575    }
576
577    /**
578     * @hide
579     * @return subtype ID. {@code 0} means that not subtype ID is specified.
580     */
581    public final int getSubtypeId() {
582        return mSubtypeId;
583    }
584
585    @Override
586    public boolean equals(Object o) {
587        if (o instanceof InputMethodSubtype) {
588            InputMethodSubtype subtype = (InputMethodSubtype) o;
589            if (subtype.mSubtypeId != 0 || mSubtypeId != 0) {
590                return (subtype.hashCode() == hashCode());
591            }
592            return (subtype.hashCode() == hashCode())
593                    && (subtype.getLocale().equals(getLocale()))
594                    && (subtype.getLanguageTag().equals(getLanguageTag()))
595                    && (subtype.getMode().equals(getMode()))
596                    && (subtype.getExtraValue().equals(getExtraValue()))
597                    && (subtype.isAuxiliary() == isAuxiliary())
598                    && (subtype.overridesImplicitlyEnabledSubtype()
599                            == overridesImplicitlyEnabledSubtype())
600                    && (subtype.isAsciiCapable() == isAsciiCapable());
601        }
602        return false;
603    }
604
605    @Override
606    public int describeContents() {
607        return 0;
608    }
609
610    @Override
611    public void writeToParcel(Parcel dest, int parcelableFlags) {
612        dest.writeInt(mSubtypeNameResId);
613        dest.writeInt(mSubtypeIconResId);
614        dest.writeString(mSubtypeLocale);
615        dest.writeString(mSubtypeLanguageTag);
616        dest.writeString(mSubtypeMode);
617        dest.writeString(mSubtypeExtraValue);
618        dest.writeInt(mIsAuxiliary ? 1 : 0);
619        dest.writeInt(mOverridesImplicitlyEnabledSubtype ? 1 : 0);
620        dest.writeInt(mSubtypeHashCode);
621        dest.writeInt(mSubtypeId);
622        dest.writeInt(mIsAsciiCapable ? 1 : 0);
623    }
624
625    public static final Parcelable.Creator<InputMethodSubtype> CREATOR
626            = new Parcelable.Creator<InputMethodSubtype>() {
627        @Override
628        public InputMethodSubtype createFromParcel(Parcel source) {
629            return new InputMethodSubtype(source);
630        }
631
632        @Override
633        public InputMethodSubtype[] newArray(int size) {
634            return new InputMethodSubtype[size];
635        }
636    };
637
638    private static int hashCodeInternal(String locale, String mode, String extraValue,
639            boolean isAuxiliary, boolean overridesImplicitlyEnabledSubtype,
640            boolean isAsciiCapable) {
641        // CAVEAT: Must revisit how to compute needsToCalculateCompatibleHashCode when a new
642        // attribute is added in order to avoid enabled subtypes being unexpectedly disabled.
643        final boolean needsToCalculateCompatibleHashCode = !isAsciiCapable;
644        if (needsToCalculateCompatibleHashCode) {
645            return Arrays.hashCode(new Object[] {locale, mode, extraValue, isAuxiliary,
646                    overridesImplicitlyEnabledSubtype});
647        }
648        return Arrays.hashCode(new Object[] {locale, mode, extraValue, isAuxiliary,
649                overridesImplicitlyEnabledSubtype, isAsciiCapable});
650    }
651
652    /**
653     * Sort the list of InputMethodSubtype
654     * @param context Context will be used for getting localized strings from IME
655     * @param flags Flags for the sort order
656     * @param imi InputMethodInfo of which subtypes are subject to be sorted
657     * @param subtypeList List of InputMethodSubtype which will be sorted
658     * @return Sorted list of subtypes
659     * @hide
660     */
661    public static List<InputMethodSubtype> sort(Context context, int flags, InputMethodInfo imi,
662            List<InputMethodSubtype> subtypeList) {
663        if (imi == null) return subtypeList;
664        final HashSet<InputMethodSubtype> inputSubtypesSet = new HashSet<InputMethodSubtype>(
665                subtypeList);
666        final ArrayList<InputMethodSubtype> sortedList = new ArrayList<InputMethodSubtype>();
667        int N = imi.getSubtypeCount();
668        for (int i = 0; i < N; ++i) {
669            InputMethodSubtype subtype = imi.getSubtypeAt(i);
670            if (inputSubtypesSet.contains(subtype)) {
671                sortedList.add(subtype);
672                inputSubtypesSet.remove(subtype);
673            }
674        }
675        // If subtypes in inputSubtypesSet remain, that means these subtypes are not
676        // contained in imi, so the remaining subtypes will be appended.
677        for (InputMethodSubtype subtype: inputSubtypesSet) {
678            sortedList.add(subtype);
679        }
680        return sortedList;
681    }
682}
683