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.content.Context;
20import android.content.pm.ApplicationInfo;
21import android.os.Parcel;
22import android.os.Parcelable;
23import android.text.TextUtils;
24import android.util.Slog;
25
26import java.util.ArrayList;
27import java.util.Arrays;
28import java.util.HashMap;
29import java.util.HashSet;
30import java.util.IllegalFormatException;
31import java.util.List;
32import java.util.Locale;
33
34/**
35 * This class is used to specify meta information of a subtype contained in an input method editor
36 * (IME). Subtype can describe locale (e.g. en_US, fr_FR...) and mode (e.g. voice, keyboard...),
37 * and is used for IME switch and settings. The input method subtype allows the system to bring up
38 * the specified subtype of the designated IME directly.
39 *
40 * <p>It should be defined in an XML resource file of the input method with the
41 * <code>&lt;subtype&gt;</code> element, which resides within an {@code &lt;input-method>} element.
42 * For more information, see the guide to
43 * <a href="{@docRoot}guide/topics/text/creating-input-method.html">
44 * Creating an Input Method</a>.</p>
45 *
46 * @see InputMethodInfo
47 *
48 * @attr ref android.R.styleable#InputMethod_Subtype_label
49 * @attr ref android.R.styleable#InputMethod_Subtype_icon
50 * @attr ref android.R.styleable#InputMethod_Subtype_imeSubtypeLocale
51 * @attr ref android.R.styleable#InputMethod_Subtype_imeSubtypeMode
52 * @attr ref android.R.styleable#InputMethod_Subtype_imeSubtypeExtraValue
53 * @attr ref android.R.styleable#InputMethod_Subtype_isAuxiliary
54 * @attr ref android.R.styleable#InputMethod_Subtype_overridesImplicitlyEnabledSubtype
55 * @attr ref android.R.styleable#InputMethod_Subtype_subtypeId
56 * @attr ref android.R.styleable#InputMethod_Subtype_isAsciiCapable
57 */
58public final class InputMethodSubtype implements Parcelable {
59    private static final String TAG = InputMethodSubtype.class.getSimpleName();
60    private static final String EXTRA_VALUE_PAIR_SEPARATOR = ",";
61    private static final String EXTRA_VALUE_KEY_VALUE_SEPARATOR = "=";
62    // TODO: remove this
63    private static final String EXTRA_KEY_UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME =
64            "UntranslatableReplacementStringInSubtypeName";
65
66    private final boolean mIsAuxiliary;
67    private final boolean mOverridesImplicitlyEnabledSubtype;
68    private final boolean mIsAsciiCapable;
69    private final int mSubtypeHashCode;
70    private final int mSubtypeIconResId;
71    private final int mSubtypeNameResId;
72    private final int mSubtypeId;
73    private final String mSubtypeLocale;
74    private final String mSubtypeMode;
75    private final String mSubtypeExtraValue;
76    private volatile HashMap<String, String> mExtraValueHashMapCache;
77
78    /**
79     * InputMethodSubtypeBuilder is a builder class of InputMethodSubtype.
80     * This class is designed to be used with
81     * {@link android.view.inputmethod.InputMethodManager#setAdditionalInputMethodSubtypes}.
82     * The developer needs to be aware of what each parameter means.
83     */
84    public static class InputMethodSubtypeBuilder {
85        /**
86         * @param isAuxiliary should true when this subtype is auxiliary, false otherwise.
87         * An auxiliary subtype has the following differences with a regular subtype:
88         * - An auxiliary subtype cannot be chosen as the default IME in Settings.
89         * - The framework will never switch to this subtype through
90         *   {@link android.view.inputmethod.InputMethodManager#switchToLastInputMethod}.
91         * Note that the subtype will still be available in the IME switcher.
92         * The intent is to allow for IMEs to specify they are meant to be invoked temporarily
93         * in a one-shot way, and to return to the previous IME once finished (e.g. voice input).
94         */
95        public InputMethodSubtypeBuilder setIsAuxiliary(boolean isAuxiliary) {
96            mIsAuxiliary = isAuxiliary;
97            return this;
98        }
99        private boolean mIsAuxiliary = false;
100
101        /**
102         * @param overridesImplicitlyEnabledSubtype should be true if this subtype should be
103         * enabled by default if no other subtypes in the IME are enabled explicitly. Note that a
104         * subtype with this parameter set will not be shown in the list of subtypes in each IME's
105         * subtype enabler. A canonical use of this would be for an IME to supply an "automatic"
106         * subtype that adapts to the current system language.
107         */
108        public InputMethodSubtypeBuilder setOverridesImplicitlyEnabledSubtype(
109                boolean overridesImplicitlyEnabledSubtype) {
110            mOverridesImplicitlyEnabledSubtype = overridesImplicitlyEnabledSubtype;
111            return this;
112        }
113        private boolean mOverridesImplicitlyEnabledSubtype = false;
114
115        /**
116         * @param isAsciiCapable should be true if this subtype is ASCII capable. If the subtype
117         * is ASCII capable, it should guarantee that the user can input ASCII characters with
118         * this subtype. This is important because many password fields only allow
119         * ASCII-characters.
120         */
121        public InputMethodSubtypeBuilder setIsAsciiCapable(boolean isAsciiCapable) {
122            mIsAsciiCapable = isAsciiCapable;
123            return this;
124        }
125        private boolean mIsAsciiCapable = false;
126
127        /**
128         * @param subtypeIconResId is a resource ID of the subtype icon drawable.
129         */
130        public InputMethodSubtypeBuilder setSubtypeIconResId(int subtypeIconResId) {
131            mSubtypeIconResId = subtypeIconResId;
132            return this;
133        }
134        private int mSubtypeIconResId = 0;
135
136        /**
137         * @param subtypeNameResId is the resource ID of the subtype name string.
138         * The string resource may have exactly one %s in it. If present,
139         * the %s part will be replaced with the locale's display name by
140         * the formatter. Please refer to {@link #getDisplayName} for details.
141         */
142        public InputMethodSubtypeBuilder setSubtypeNameResId(int subtypeNameResId) {
143            mSubtypeNameResId = subtypeNameResId;
144            return this;
145        }
146        private int mSubtypeNameResId = 0;
147
148        /**
149         * @param subtypeId is the unique ID for this subtype. The input method framework keeps
150         * track of enabled subtypes by ID. When the IME package gets upgraded, enabled IDs will
151         * stay enabled even if other attributes are different. If the ID is unspecified or 0,
152         * Arrays.hashCode(new Object[] {locale, mode, extraValue,
153         * isAuxiliary, overridesImplicitlyEnabledSubtype}) will be used instead.
154         */
155        public InputMethodSubtypeBuilder setSubtypeId(int subtypeId) {
156            mSubtypeId = subtypeId;
157            return this;
158        }
159        private int mSubtypeId = 0;
160
161        /**
162         * @param subtypeLocale is the locale supported by this subtype.
163         */
164        public InputMethodSubtypeBuilder setSubtypeLocale(String subtypeLocale) {
165            mSubtypeLocale = subtypeLocale == null ? "" : subtypeLocale;
166            return this;
167        }
168        private String mSubtypeLocale = "";
169
170        /**
171         * @param subtypeMode is the mode supported by this subtype.
172         */
173        public InputMethodSubtypeBuilder setSubtypeMode(String subtypeMode) {
174            mSubtypeMode = subtypeMode == null ? "" : subtypeMode;
175            return this;
176        }
177        private String mSubtypeMode = "";
178        /**
179         * @param subtypeExtraValue is the extra value of the subtype. This string is free-form,
180         * but the API supplies tools to deal with a key-value comma-separated list; see
181         * {@link #containsExtraValueKey} and {@link #getExtraValueOf}.
182         */
183        public InputMethodSubtypeBuilder setSubtypeExtraValue(String subtypeExtraValue) {
184            mSubtypeExtraValue = subtypeExtraValue == null ? "" : subtypeExtraValue;
185            return this;
186        }
187        private String mSubtypeExtraValue = "";
188
189        /**
190         * @return InputMethodSubtype using parameters in this InputMethodSubtypeBuilder.
191         */
192        public InputMethodSubtype build() {
193            return new InputMethodSubtype(this);
194        }
195     }
196
197     private static InputMethodSubtypeBuilder getBuilder(int nameId, int iconId, String locale,
198             String mode, String extraValue, boolean isAuxiliary,
199             boolean overridesImplicitlyEnabledSubtype, int id, boolean isAsciiCapable) {
200         final InputMethodSubtypeBuilder builder = new InputMethodSubtypeBuilder();
201         builder.mSubtypeNameResId = nameId;
202         builder.mSubtypeIconResId = iconId;
203         builder.mSubtypeLocale = locale;
204         builder.mSubtypeMode = mode;
205         builder.mSubtypeExtraValue = extraValue;
206         builder.mIsAuxiliary = isAuxiliary;
207         builder.mOverridesImplicitlyEnabledSubtype = overridesImplicitlyEnabledSubtype;
208         builder.mSubtypeId = id;
209         builder.mIsAsciiCapable = isAsciiCapable;
210         return builder;
211     }
212
213    /**
214     * Constructor with no subtype ID specified.
215     * @deprecated use {@link InputMethodSubtypeBuilder} instead.
216     * Arguments for this constructor have the same meanings as
217     * {@link InputMethodSubtype#InputMethodSubtype(int, int, String, String, String, boolean,
218     * boolean, int)} except "id".
219     */
220    public InputMethodSubtype(int nameId, int iconId, String locale, String mode, String extraValue,
221            boolean isAuxiliary, boolean overridesImplicitlyEnabledSubtype) {
222        this(nameId, iconId, locale, mode, extraValue, isAuxiliary,
223                overridesImplicitlyEnabledSubtype, 0);
224    }
225
226    /**
227     * Constructor.
228     * @deprecated use {@link InputMethodSubtypeBuilder} instead.
229     * "isAsciiCapable" is "false" in this constructor.
230     * @param nameId Resource ID of the subtype name string. The string resource may have exactly
231     * one %s in it. If there is, the %s part will be replaced with the locale's display name by
232     * the formatter. Please refer to {@link #getDisplayName} for details.
233     * @param iconId Resource ID of the subtype icon drawable.
234     * @param locale The locale supported by the subtype
235     * @param mode The mode supported by the subtype
236     * @param extraValue The extra value of the subtype. This string is free-form, but the API
237     * supplies tools to deal with a key-value comma-separated list; see
238     * {@link #containsExtraValueKey} and {@link #getExtraValueOf}.
239     * @param isAuxiliary true when this subtype is auxiliary, false otherwise. An auxiliary
240     * subtype will not be shown in the list of enabled IMEs for choosing the current IME in
241     * the Settings even when this subtype is enabled. Please note that this subtype will still
242     * be shown in the list of IMEs in the IME switcher to allow the user to tentatively switch
243     * to this subtype while an IME is shown. The framework will never switch the current IME to
244     * this subtype by {@link android.view.inputmethod.InputMethodManager#switchToLastInputMethod}.
245     * The intent of having this flag is to allow for IMEs that are invoked in a one-shot way as
246     * auxiliary input mode, and return to the previous IME once it is finished (e.g. voice input).
247     * @param overridesImplicitlyEnabledSubtype true when this subtype should be enabled by default
248     * if no other subtypes in the IME are enabled explicitly. Note that a subtype with this
249     * parameter being true will not be shown in the list of subtypes in each IME's subtype enabler.
250     * Having an "automatic" subtype is an example use of this flag.
251     * @param id The unique ID for the subtype. The input method framework keeps track of enabled
252     * subtypes by ID. When the IME package gets upgraded, enabled IDs will stay enabled even if
253     * other attributes are different. If the ID is unspecified or 0,
254     * Arrays.hashCode(new Object[] {locale, mode, extraValue,
255     * isAuxiliary, overridesImplicitlyEnabledSubtype}) will be used instead.
256     */
257    public InputMethodSubtype(int nameId, int iconId, String locale, String mode, String extraValue,
258            boolean isAuxiliary, boolean overridesImplicitlyEnabledSubtype, int id) {
259        this(getBuilder(nameId, iconId, locale, mode, extraValue, isAuxiliary,
260                overridesImplicitlyEnabledSubtype, id, false));
261    }
262
263    /**
264     * Constructor.
265     * @param builder Builder for InputMethodSubtype
266     */
267    private InputMethodSubtype(InputMethodSubtypeBuilder builder) {
268        mSubtypeNameResId = builder.mSubtypeNameResId;
269        mSubtypeIconResId = builder.mSubtypeIconResId;
270        mSubtypeLocale = builder.mSubtypeLocale;
271        mSubtypeMode = builder.mSubtypeMode;
272        mSubtypeExtraValue = builder.mSubtypeExtraValue;
273        mIsAuxiliary = builder.mIsAuxiliary;
274        mOverridesImplicitlyEnabledSubtype = builder.mOverridesImplicitlyEnabledSubtype;
275        mSubtypeId = builder.mSubtypeId;
276        mIsAsciiCapable = builder.mIsAsciiCapable;
277        // If hashCode() of this subtype is 0 and you want to specify it as an id of this subtype,
278        // just specify 0 as this subtype's id. Then, this subtype's id is treated as 0.
279        mSubtypeHashCode = mSubtypeId != 0 ? mSubtypeId : hashCodeInternal(mSubtypeLocale,
280                mSubtypeMode, mSubtypeExtraValue, mIsAuxiliary, mOverridesImplicitlyEnabledSubtype,
281                mIsAsciiCapable);
282    }
283
284    InputMethodSubtype(Parcel source) {
285        String s;
286        mSubtypeNameResId = source.readInt();
287        mSubtypeIconResId = source.readInt();
288        s = source.readString();
289        mSubtypeLocale = s != null ? s : "";
290        s = source.readString();
291        mSubtypeMode = s != null ? s : "";
292        s = source.readString();
293        mSubtypeExtraValue = s != null ? s : "";
294        mIsAuxiliary = (source.readInt() == 1);
295        mOverridesImplicitlyEnabledSubtype = (source.readInt() == 1);
296        mSubtypeHashCode = source.readInt();
297        mSubtypeId = source.readInt();
298        mIsAsciiCapable = (source.readInt() == 1);
299    }
300
301    /**
302     * @return Resource ID of the subtype name string.
303     */
304    public int getNameResId() {
305        return mSubtypeNameResId;
306    }
307
308    /**
309     * @return Resource ID of the subtype icon drawable.
310     */
311    public int getIconResId() {
312        return mSubtypeIconResId;
313    }
314
315    /**
316     * @return The locale of the subtype. This method returns the "locale" string parameter passed
317     * to the constructor.
318     */
319    public String getLocale() {
320        return mSubtypeLocale;
321    }
322
323    /**
324     * @return The mode of the subtype.
325     */
326    public String getMode() {
327        return mSubtypeMode;
328    }
329
330    /**
331     * @return The extra value of the subtype.
332     */
333    public String getExtraValue() {
334        return mSubtypeExtraValue;
335    }
336
337    /**
338     * @return true if this subtype is auxiliary, false otherwise. An auxiliary subtype will not be
339     * shown in the list of enabled IMEs for choosing the current IME in the Settings even when this
340     * subtype is enabled. Please note that this subtype will still be shown in the list of IMEs in
341     * the IME switcher to allow the user to tentatively switch to this subtype while an IME is
342     * shown. The framework will never switch the current IME to this subtype by
343     * {@link android.view.inputmethod.InputMethodManager#switchToLastInputMethod}.
344     * The intent of having this flag is to allow for IMEs that are invoked in a one-shot way as
345     * auxiliary input mode, and return to the previous IME once it is finished (e.g. voice input).
346     */
347    public boolean isAuxiliary() {
348        return mIsAuxiliary;
349    }
350
351    /**
352     * @return true when this subtype will be enabled by default if no other subtypes in the IME
353     * are enabled explicitly, false otherwise. Note that a subtype with this method returning true
354     * will not be shown in the list of subtypes in each IME's subtype enabler. Having an
355     * "automatic" subtype is an example use of this flag.
356     */
357    public boolean overridesImplicitlyEnabledSubtype() {
358        return mOverridesImplicitlyEnabledSubtype;
359    }
360
361    /**
362     * @return true if this subtype is Ascii capable, false otherwise. If the subtype is ASCII
363     * capable, it should guarantee that the user can input ASCII characters with this subtype.
364     * This is important because many password fields only allow ASCII-characters.
365     */
366    public boolean isAsciiCapable() {
367        return mIsAsciiCapable;
368    }
369
370    /**
371     * @param context Context will be used for getting Locale and PackageManager.
372     * @param packageName The package name of the IME
373     * @param appInfo The application info of the IME
374     * @return a display name for this subtype. The string resource of the label (mSubtypeNameResId)
375     * may have exactly one %s in it. If there is, the %s part will be replaced with the locale's
376     * display name by the formatter. If there is not, this method returns the string specified by
377     * mSubtypeNameResId. If mSubtypeNameResId is not specified (== 0), it's up to the framework to
378     * generate an appropriate display name.
379     */
380    public CharSequence getDisplayName(
381            Context context, String packageName, ApplicationInfo appInfo) {
382        final Locale locale = constructLocaleFromString(mSubtypeLocale);
383        final String localeStr = locale != null ? locale.getDisplayName() : mSubtypeLocale;
384        if (mSubtypeNameResId == 0) {
385            return localeStr;
386        }
387        final CharSequence subtypeName = context.getPackageManager().getText(
388                packageName, mSubtypeNameResId, appInfo);
389        if (!TextUtils.isEmpty(subtypeName)) {
390            final String replacementString =
391                    containsExtraValueKey(EXTRA_KEY_UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME)
392                            ? getExtraValueOf(EXTRA_KEY_UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME)
393                            : localeStr;
394            try {
395                return String.format(
396                        subtypeName.toString(), replacementString != null ? replacementString : "");
397            } catch (IllegalFormatException e) {
398                Slog.w(TAG, "Found illegal format in subtype name("+ subtypeName + "): " + e);
399                return "";
400            }
401        } else {
402            return localeStr;
403        }
404    }
405
406    private HashMap<String, String> getExtraValueHashMap() {
407        if (mExtraValueHashMapCache == null) {
408            synchronized(this) {
409                if (mExtraValueHashMapCache == null) {
410                    mExtraValueHashMapCache = new HashMap<String, String>();
411                    final String[] pairs = mSubtypeExtraValue.split(EXTRA_VALUE_PAIR_SEPARATOR);
412                    final int N = pairs.length;
413                    for (int i = 0; i < N; ++i) {
414                        final String[] pair = pairs[i].split(EXTRA_VALUE_KEY_VALUE_SEPARATOR);
415                        if (pair.length == 1) {
416                            mExtraValueHashMapCache.put(pair[0], null);
417                        } else if (pair.length > 1) {
418                            if (pair.length > 2) {
419                                Slog.w(TAG, "ExtraValue has two or more '='s");
420                            }
421                            mExtraValueHashMapCache.put(pair[0], pair[1]);
422                        }
423                    }
424                }
425            }
426        }
427        return mExtraValueHashMapCache;
428    }
429
430    /**
431     * The string of ExtraValue in subtype should be defined as follows:
432     * example: key0,key1=value1,key2,key3,key4=value4
433     * @param key The key of extra value
434     * @return The subtype contains specified the extra value
435     */
436    public boolean containsExtraValueKey(String key) {
437        return getExtraValueHashMap().containsKey(key);
438    }
439
440    /**
441     * The string of ExtraValue in subtype should be defined as follows:
442     * example: key0,key1=value1,key2,key3,key4=value4
443     * @param key The key of extra value
444     * @return The value of the specified key
445     */
446    public String getExtraValueOf(String key) {
447        return getExtraValueHashMap().get(key);
448    }
449
450    @Override
451    public int hashCode() {
452        return mSubtypeHashCode;
453    }
454
455    @Override
456    public boolean equals(Object o) {
457        if (o instanceof InputMethodSubtype) {
458            InputMethodSubtype subtype = (InputMethodSubtype) o;
459            if (subtype.mSubtypeId != 0 || mSubtypeId != 0) {
460                return (subtype.hashCode() == hashCode());
461            }
462            return (subtype.hashCode() == hashCode())
463                && (subtype.getLocale().equals(getLocale()))
464                && (subtype.getMode().equals(getMode()))
465                && (subtype.getExtraValue().equals(getExtraValue()))
466                && (subtype.isAuxiliary() == isAuxiliary())
467                && (subtype.overridesImplicitlyEnabledSubtype()
468                        == overridesImplicitlyEnabledSubtype())
469                && (subtype.isAsciiCapable() == isAsciiCapable());
470        }
471        return false;
472    }
473
474    @Override
475    public int describeContents() {
476        return 0;
477    }
478
479    @Override
480    public void writeToParcel(Parcel dest, int parcelableFlags) {
481        dest.writeInt(mSubtypeNameResId);
482        dest.writeInt(mSubtypeIconResId);
483        dest.writeString(mSubtypeLocale);
484        dest.writeString(mSubtypeMode);
485        dest.writeString(mSubtypeExtraValue);
486        dest.writeInt(mIsAuxiliary ? 1 : 0);
487        dest.writeInt(mOverridesImplicitlyEnabledSubtype ? 1 : 0);
488        dest.writeInt(mSubtypeHashCode);
489        dest.writeInt(mSubtypeId);
490        dest.writeInt(mIsAsciiCapable ? 1 : 0);
491    }
492
493    public static final Parcelable.Creator<InputMethodSubtype> CREATOR
494            = new Parcelable.Creator<InputMethodSubtype>() {
495        @Override
496        public InputMethodSubtype createFromParcel(Parcel source) {
497            return new InputMethodSubtype(source);
498        }
499
500        @Override
501        public InputMethodSubtype[] newArray(int size) {
502            return new InputMethodSubtype[size];
503        }
504    };
505
506    private static Locale constructLocaleFromString(String localeStr) {
507        if (TextUtils.isEmpty(localeStr))
508            return null;
509        String[] localeParams = localeStr.split("_", 3);
510        // The length of localeStr is guaranteed to always return a 1 <= value <= 3
511        // because localeStr is not empty.
512        if (localeParams.length == 1) {
513            return new Locale(localeParams[0]);
514        } else if (localeParams.length == 2) {
515            return new Locale(localeParams[0], localeParams[1]);
516        } else if (localeParams.length == 3) {
517            return new Locale(localeParams[0], localeParams[1], localeParams[2]);
518        }
519        return null;
520    }
521
522    private static int hashCodeInternal(String locale, String mode, String extraValue,
523            boolean isAuxiliary, boolean overridesImplicitlyEnabledSubtype,
524            boolean isAsciiCapable) {
525        // CAVEAT: Must revisit how to compute needsToCalculateCompatibleHashCode when a new
526        // attribute is added in order to avoid enabled subtypes being unexpectedly disabled.
527        final boolean needsToCalculateCompatibleHashCode = !isAsciiCapable;
528        if (needsToCalculateCompatibleHashCode) {
529            return Arrays.hashCode(new Object[] {locale, mode, extraValue, isAuxiliary,
530                    overridesImplicitlyEnabledSubtype});
531        }
532        return Arrays.hashCode(new Object[] {locale, mode, extraValue, isAuxiliary,
533                overridesImplicitlyEnabledSubtype, isAsciiCapable});
534    }
535
536    /**
537     * Sort the list of InputMethodSubtype
538     * @param context Context will be used for getting localized strings from IME
539     * @param flags Flags for the sort order
540     * @param imi InputMethodInfo of which subtypes are subject to be sorted
541     * @param subtypeList List of InputMethodSubtype which will be sorted
542     * @return Sorted list of subtypes
543     * @hide
544     */
545    public static List<InputMethodSubtype> sort(Context context, int flags, InputMethodInfo imi,
546            List<InputMethodSubtype> subtypeList) {
547        if (imi == null) return subtypeList;
548        final HashSet<InputMethodSubtype> inputSubtypesSet = new HashSet<InputMethodSubtype>(
549                subtypeList);
550        final ArrayList<InputMethodSubtype> sortedList = new ArrayList<InputMethodSubtype>();
551        int N = imi.getSubtypeCount();
552        for (int i = 0; i < N; ++i) {
553            InputMethodSubtype subtype = imi.getSubtypeAt(i);
554            if (inputSubtypesSet.contains(subtype)) {
555                sortedList.add(subtype);
556                inputSubtypesSet.remove(subtype);
557            }
558        }
559        // If subtypes in inputSubtypesSet remain, that means these subtypes are not
560        // contained in imi, so the remaining subtypes will be appended.
561        for (InputMethodSubtype subtype: inputSubtypesSet) {
562            sortedList.add(subtype);
563        }
564        return sortedList;
565    }
566}
567