Java tutorial
/* * Copyright (C) 2010 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.view.inputmethod; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.res.Configuration; import android.icu.text.DisplayContext; import android.icu.text.LocaleDisplayNames; import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; import android.util.Slog; import com.android.internal.inputmethod.InputMethodUtils; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.IllegalFormatException; import java.util.List; import java.util.Locale; /** * This class is used to specify meta information of a subtype contained in an input method editor * (IME). Subtype can describe locale (e.g. en_US, fr_FR...) and mode (e.g. voice, keyboard...), * and is used for IME switch and settings. The input method subtype allows the system to bring up * the specified subtype of the designated IME directly. * * <p>It should be defined in an XML resource file of the input method with the * <code><subtype></code> element, which resides within an {@code <input-method>} element. * For more information, see the guide to * <a href="{@docRoot}guide/topics/text/creating-input-method.html"> * Creating an Input Method</a>.</p> * * @see InputMethodInfo * * @attr ref android.R.styleable#InputMethod_Subtype_label * @attr ref android.R.styleable#InputMethod_Subtype_icon * @attr ref android.R.styleable#InputMethod_Subtype_languageTag * @attr ref android.R.styleable#InputMethod_Subtype_imeSubtypeLocale * @attr ref android.R.styleable#InputMethod_Subtype_imeSubtypeMode * @attr ref android.R.styleable#InputMethod_Subtype_imeSubtypeExtraValue * @attr ref android.R.styleable#InputMethod_Subtype_isAuxiliary * @attr ref android.R.styleable#InputMethod_Subtype_overridesImplicitlyEnabledSubtype * @attr ref android.R.styleable#InputMethod_Subtype_subtypeId * @attr ref android.R.styleable#InputMethod_Subtype_isAsciiCapable */ public final class InputMethodSubtype implements Parcelable { private static final String TAG = InputMethodSubtype.class.getSimpleName(); private static final String LANGUAGE_TAG_NONE = ""; private static final String EXTRA_VALUE_PAIR_SEPARATOR = ","; private static final String EXTRA_VALUE_KEY_VALUE_SEPARATOR = "="; // TODO: remove this private static final String EXTRA_KEY_UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME = "UntranslatableReplacementStringInSubtypeName"; private static final int SUBTYPE_ID_NONE = 0; private final boolean mIsAuxiliary; private final boolean mOverridesImplicitlyEnabledSubtype; private final boolean mIsAsciiCapable; private final int mSubtypeHashCode; private final int mSubtypeIconResId; private final int mSubtypeNameResId; private final int mSubtypeId; private final String mSubtypeLocale; private final String mSubtypeLanguageTag; private final String mSubtypeMode; private final String mSubtypeExtraValue; private final Object mLock = new Object(); private volatile Locale mCachedLocaleObj; private volatile HashMap<String, String> mExtraValueHashMapCache; /** * InputMethodSubtypeBuilder is a builder class of InputMethodSubtype. * This class is designed to be used with * {@link android.view.inputmethod.InputMethodManager#setAdditionalInputMethodSubtypes}. * The developer needs to be aware of what each parameter means. */ public static class InputMethodSubtypeBuilder { /** * @param isAuxiliary should true when this subtype is auxiliary, false otherwise. * An auxiliary subtype has the following differences with a regular subtype: * - An auxiliary subtype cannot be chosen as the default IME in Settings. * - The framework will never switch to this subtype through * {@link android.view.inputmethod.InputMethodManager#switchToLastInputMethod}. * Note that the subtype will still be available in the IME switcher. * The intent is to allow for IMEs to specify they are meant to be invoked temporarily * in a one-shot way, and to return to the previous IME once finished (e.g. voice input). */ public InputMethodSubtypeBuilder setIsAuxiliary(boolean isAuxiliary) { mIsAuxiliary = isAuxiliary; return this; } private boolean mIsAuxiliary = false; /** * @param overridesImplicitlyEnabledSubtype should be true if this subtype should be * enabled by default if no other subtypes in the IME are enabled explicitly. Note that a * subtype with this parameter set will not be shown in the list of subtypes in each IME's * subtype enabler. A canonical use of this would be for an IME to supply an "automatic" * subtype that adapts to the current system language. */ public InputMethodSubtypeBuilder setOverridesImplicitlyEnabledSubtype( boolean overridesImplicitlyEnabledSubtype) { mOverridesImplicitlyEnabledSubtype = overridesImplicitlyEnabledSubtype; return this; } private boolean mOverridesImplicitlyEnabledSubtype = false; /** * @param isAsciiCapable should be true if this subtype is ASCII capable. If the subtype * is ASCII capable, it should guarantee that the user can input ASCII characters with * this subtype. This is important because many password fields only allow * ASCII-characters. */ public InputMethodSubtypeBuilder setIsAsciiCapable(boolean isAsciiCapable) { mIsAsciiCapable = isAsciiCapable; return this; } private boolean mIsAsciiCapable = false; /** * @param subtypeIconResId is a resource ID of the subtype icon drawable. */ public InputMethodSubtypeBuilder setSubtypeIconResId(int subtypeIconResId) { mSubtypeIconResId = subtypeIconResId; return this; } private int mSubtypeIconResId = 0; /** * @param subtypeNameResId is the resource ID of the subtype name string. * The string resource may have exactly one %s in it. If present, * the %s part will be replaced with the locale's display name by * the formatter. Please refer to {@link #getDisplayName} for details. */ public InputMethodSubtypeBuilder setSubtypeNameResId(int subtypeNameResId) { mSubtypeNameResId = subtypeNameResId; return this; } private int mSubtypeNameResId = 0; /** * @param subtypeId is the unique ID for this subtype. The input method framework keeps * track of enabled subtypes by ID. When the IME package gets upgraded, enabled IDs will * stay enabled even if other attributes are different. If the ID is unspecified or 0, * Arrays.hashCode(new Object[] {locale, mode, extraValue, * isAuxiliary, overridesImplicitlyEnabledSubtype, isAsciiCapable}) will be used instead. */ public InputMethodSubtypeBuilder setSubtypeId(int subtypeId) { mSubtypeId = subtypeId; return this; } private int mSubtypeId = SUBTYPE_ID_NONE; /** * @param subtypeLocale is the locale supported by this subtype. */ public InputMethodSubtypeBuilder setSubtypeLocale(String subtypeLocale) { mSubtypeLocale = subtypeLocale == null ? "" : subtypeLocale; return this; } private String mSubtypeLocale = ""; /** * @param languageTag is the BCP-47 Language Tag supported by this subtype. */ public InputMethodSubtypeBuilder setLanguageTag(String languageTag) { mSubtypeLanguageTag = languageTag == null ? LANGUAGE_TAG_NONE : languageTag; return this; } private String mSubtypeLanguageTag = LANGUAGE_TAG_NONE; /** * @param subtypeMode is the mode supported by this subtype. */ public InputMethodSubtypeBuilder setSubtypeMode(String subtypeMode) { mSubtypeMode = subtypeMode == null ? "" : subtypeMode; return this; } private String mSubtypeMode = ""; /** * @param subtypeExtraValue is the extra value of the subtype. This string is free-form, * but the API supplies tools to deal with a key-value comma-separated list; see * {@link #containsExtraValueKey} and {@link #getExtraValueOf}. */ public InputMethodSubtypeBuilder setSubtypeExtraValue(String subtypeExtraValue) { mSubtypeExtraValue = subtypeExtraValue == null ? "" : subtypeExtraValue; return this; } private String mSubtypeExtraValue = ""; /** * @return InputMethodSubtype using parameters in this InputMethodSubtypeBuilder. */ public InputMethodSubtype build() { return new InputMethodSubtype(this); } } private static InputMethodSubtypeBuilder getBuilder(int nameId, int iconId, String locale, String mode, String extraValue, boolean isAuxiliary, boolean overridesImplicitlyEnabledSubtype, int id, boolean isAsciiCapable) { final InputMethodSubtypeBuilder builder = new InputMethodSubtypeBuilder(); builder.mSubtypeNameResId = nameId; builder.mSubtypeIconResId = iconId; builder.mSubtypeLocale = locale; builder.mSubtypeMode = mode; builder.mSubtypeExtraValue = extraValue; builder.mIsAuxiliary = isAuxiliary; builder.mOverridesImplicitlyEnabledSubtype = overridesImplicitlyEnabledSubtype; builder.mSubtypeId = id; builder.mIsAsciiCapable = isAsciiCapable; return builder; } /** * Constructor with no subtype ID specified. * @deprecated use {@link InputMethodSubtypeBuilder} instead. * Arguments for this constructor have the same meanings as * {@link InputMethodSubtype#InputMethodSubtype(int, int, String, String, String, boolean, * boolean, int)} except "id". */ @Deprecated public InputMethodSubtype(int nameId, int iconId, String locale, String mode, String extraValue, boolean isAuxiliary, boolean overridesImplicitlyEnabledSubtype) { this(nameId, iconId, locale, mode, extraValue, isAuxiliary, overridesImplicitlyEnabledSubtype, 0); } /** * Constructor. * @deprecated use {@link InputMethodSubtypeBuilder} instead. * "isAsciiCapable" is "false" in this constructor. * @param nameId Resource ID of the subtype name string. The string resource may have exactly * one %s in it. If there is, the %s part will be replaced with the locale's display name by * the formatter. Please refer to {@link #getDisplayName} for details. * @param iconId Resource ID of the subtype icon drawable. * @param locale The locale supported by the subtype * @param mode The mode supported by the subtype * @param extraValue The extra value of the subtype. This string is free-form, but the API * supplies tools to deal with a key-value comma-separated list; see * {@link #containsExtraValueKey} and {@link #getExtraValueOf}. * @param isAuxiliary true when this subtype is auxiliary, false otherwise. An auxiliary * subtype will not be shown in the list of enabled IMEs for choosing the current IME in * the Settings even when this subtype is enabled. Please note that this subtype will still * be shown in the list of IMEs in the IME switcher to allow the user to tentatively switch * to this subtype while an IME is shown. The framework will never switch the current IME to * this subtype by {@link android.view.inputmethod.InputMethodManager#switchToLastInputMethod}. * The intent of having this flag is to allow for IMEs that are invoked in a one-shot way as * auxiliary input mode, and return to the previous IME once it is finished (e.g. voice input). * @param overridesImplicitlyEnabledSubtype true when this subtype should be enabled by default * if no other subtypes in the IME are enabled explicitly. Note that a subtype with this * parameter being true will not be shown in the list of subtypes in each IME's subtype enabler. * Having an "automatic" subtype is an example use of this flag. * @param id The unique ID for the subtype. The input method framework keeps track of enabled * subtypes by ID. When the IME package gets upgraded, enabled IDs will stay enabled even if * other attributes are different. If the ID is unspecified or 0, * Arrays.hashCode(new Object[] {locale, mode, extraValue, * isAuxiliary, overridesImplicitlyEnabledSubtype, isAsciiCapable}) will be used instead. */ @Deprecated public InputMethodSubtype(int nameId, int iconId, String locale, String mode, String extraValue, boolean isAuxiliary, boolean overridesImplicitlyEnabledSubtype, int id) { this(getBuilder(nameId, iconId, locale, mode, extraValue, isAuxiliary, overridesImplicitlyEnabledSubtype, id, false)); } /** * Constructor. * @param builder Builder for InputMethodSubtype */ private InputMethodSubtype(InputMethodSubtypeBuilder builder) { mSubtypeNameResId = builder.mSubtypeNameResId; mSubtypeIconResId = builder.mSubtypeIconResId; mSubtypeLocale = builder.mSubtypeLocale; mSubtypeLanguageTag = builder.mSubtypeLanguageTag; mSubtypeMode = builder.mSubtypeMode; mSubtypeExtraValue = builder.mSubtypeExtraValue; mIsAuxiliary = builder.mIsAuxiliary; mOverridesImplicitlyEnabledSubtype = builder.mOverridesImplicitlyEnabledSubtype; mSubtypeId = builder.mSubtypeId; mIsAsciiCapable = builder.mIsAsciiCapable; // If hashCode() of this subtype is 0 and you want to specify it as an id of this subtype, // just specify 0 as this subtype's id. Then, this subtype's id is treated as 0. if (mSubtypeId != SUBTYPE_ID_NONE) { mSubtypeHashCode = mSubtypeId; } else { mSubtypeHashCode = hashCodeInternal(mSubtypeLocale, mSubtypeMode, mSubtypeExtraValue, mIsAuxiliary, mOverridesImplicitlyEnabledSubtype, mIsAsciiCapable); } } InputMethodSubtype(Parcel source) { String s; mSubtypeNameResId = source.readInt(); mSubtypeIconResId = source.readInt(); s = source.readString(); mSubtypeLocale = s != null ? s : ""; s = source.readString(); mSubtypeLanguageTag = s != null ? s : LANGUAGE_TAG_NONE; s = source.readString(); mSubtypeMode = s != null ? s : ""; s = source.readString(); mSubtypeExtraValue = s != null ? s : ""; mIsAuxiliary = (source.readInt() == 1); mOverridesImplicitlyEnabledSubtype = (source.readInt() == 1); mSubtypeHashCode = source.readInt(); mSubtypeId = source.readInt(); mIsAsciiCapable = (source.readInt() == 1); } /** * @return Resource ID of the subtype name string. */ public int getNameResId() { return mSubtypeNameResId; } /** * @return Resource ID of the subtype icon drawable. */ public int getIconResId() { return mSubtypeIconResId; } /** * @return The locale of the subtype. This method returns the "locale" string parameter passed * to the constructor. * * @deprecated Use {@link #getLanguageTag()} instead. */ @Deprecated @NonNull public String getLocale() { return mSubtypeLocale; } /** * @return the BCP-47 Language Tag of the subtype. Returns an empty string when no Language Tag * is specified. * * @see Locale#forLanguageTag(String) */ @NonNull public String getLanguageTag() { return mSubtypeLanguageTag; } /** * @return {@link Locale} constructed from {@link #getLanguageTag()}. If the Language Tag is not * specified, then try to construct from {@link #getLocale()} * * <p>TODO: Consider to make this a public API, or move this to support lib.</p> * @hide */ @Nullable public Locale getLocaleObject() { if (mCachedLocaleObj != null) { return mCachedLocaleObj; } synchronized (mLock) { if (mCachedLocaleObj != null) { return mCachedLocaleObj; } if (!TextUtils.isEmpty(mSubtypeLanguageTag)) { mCachedLocaleObj = Locale.forLanguageTag(mSubtypeLanguageTag); } else { mCachedLocaleObj = InputMethodUtils.constructLocaleFromString(mSubtypeLocale); } return mCachedLocaleObj; } } /** * @return The mode of the subtype. */ public String getMode() { return mSubtypeMode; } /** * @return The extra value of the subtype. */ public String getExtraValue() { return mSubtypeExtraValue; } /** * @return true if this subtype is auxiliary, false otherwise. An auxiliary subtype will not be * shown in the list of enabled IMEs for choosing the current IME in the Settings even when this * subtype is enabled. Please note that this subtype will still be shown in the list of IMEs in * the IME switcher to allow the user to tentatively switch to this subtype while an IME is * shown. The framework will never switch the current IME to this subtype by * {@link android.view.inputmethod.InputMethodManager#switchToLastInputMethod}. * The intent of having this flag is to allow for IMEs that are invoked in a one-shot way as * auxiliary input mode, and return to the previous IME once it is finished (e.g. voice input). */ public boolean isAuxiliary() { return mIsAuxiliary; } /** * @return true when this subtype will be enabled by default if no other subtypes in the IME * are enabled explicitly, false otherwise. Note that a subtype with this method returning true * will not be shown in the list of subtypes in each IME's subtype enabler. Having an * "automatic" subtype is an example use of this flag. */ public boolean overridesImplicitlyEnabledSubtype() { return mOverridesImplicitlyEnabledSubtype; } /** * @return true if this subtype is Ascii capable, false otherwise. If the subtype is ASCII * capable, it should guarantee that the user can input ASCII characters with this subtype. * This is important because many password fields only allow ASCII-characters. */ public boolean isAsciiCapable() { return mIsAsciiCapable; } /** * Returns a display name for this subtype. * * <p>If {@code subtypeNameResId} is specified (!= 0) text generated from that resource will * be returned. The localized string resource of the label should be capitalized for inclusion * in UI lists. The string resource may contain at most one {@code %s}. If present, the * {@code %s} will be replaced with the display name of the subtype locale in the user's locale. * * <p>If {@code subtypeNameResId} is not specified (== 0) the framework returns the display name * of the subtype locale, as capitalized for use in UI lists, in the user's locale. * * @param context {@link Context} will be used for getting {@link Locale} and * {@link android.content.pm.PackageManager}. * @param packageName The package name of the input method. * @param appInfo The {@link ApplicationInfo} of the input method. * @return a display name for this subtype. */ @NonNull public CharSequence getDisplayName(Context context, String packageName, ApplicationInfo appInfo) { if (mSubtypeNameResId == 0) { return getLocaleDisplayName(getLocaleFromContext(context), getLocaleObject(), DisplayContext.CAPITALIZATION_FOR_UI_LIST_OR_MENU); } final CharSequence subtypeName = context.getPackageManager().getText(packageName, mSubtypeNameResId, appInfo); if (TextUtils.isEmpty(subtypeName)) { return ""; } final String subtypeNameString = subtypeName.toString(); String replacementString; if (containsExtraValueKey(EXTRA_KEY_UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME)) { replacementString = getExtraValueOf(EXTRA_KEY_UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME); } else { final DisplayContext displayContext; if (TextUtils.equals(subtypeNameString, "%s")) { displayContext = DisplayContext.CAPITALIZATION_FOR_UI_LIST_OR_MENU; } else if (subtypeNameString.startsWith("%s")) { displayContext = DisplayContext.CAPITALIZATION_FOR_BEGINNING_OF_SENTENCE; } else { displayContext = DisplayContext.CAPITALIZATION_FOR_MIDDLE_OF_SENTENCE; } replacementString = getLocaleDisplayName(getLocaleFromContext(context), getLocaleObject(), displayContext); } if (replacementString == null) { replacementString = ""; } try { return String.format(subtypeNameString, replacementString); } catch (IllegalFormatException e) { Slog.w(TAG, "Found illegal format in subtype name(" + subtypeName + "): " + e); return ""; } } @Nullable private static Locale getLocaleFromContext(@Nullable final Context context) { if (context == null) { return null; } if (context.getResources() == null) { return null; } final Configuration configuration = context.getResources().getConfiguration(); if (configuration == null) { return null; } return configuration.getLocales().get(0); } /** * @param displayLocale {@link Locale} to be used to display {@code localeToDisplay} * @param localeToDisplay {@link Locale} to be displayed in {@code displayLocale} * @param displayContext context parameter to be used to display {@code localeToDisplay} in * {@code displayLocale} * @return Returns the name of the {@code localeToDisplay} in the user's current locale. */ @NonNull private static String getLocaleDisplayName(@Nullable Locale displayLocale, @Nullable Locale localeToDisplay, final DisplayContext displayContext) { if (localeToDisplay == null) { return ""; } final Locale nonNullDisplayLocale = displayLocale != null ? displayLocale : Locale.getDefault(); return LocaleDisplayNames.getInstance(nonNullDisplayLocale, displayContext) .localeDisplayName(localeToDisplay); } private HashMap<String, String> getExtraValueHashMap() { synchronized (this) { HashMap<String, String> extraValueMap = mExtraValueHashMapCache; if (extraValueMap != null) { return extraValueMap; } extraValueMap = new HashMap<>(); final String[] pairs = mSubtypeExtraValue.split(EXTRA_VALUE_PAIR_SEPARATOR); for (int i = 0; i < pairs.length; ++i) { final String[] pair = pairs[i].split(EXTRA_VALUE_KEY_VALUE_SEPARATOR); if (pair.length == 1) { extraValueMap.put(pair[0], null); } else if (pair.length > 1) { if (pair.length > 2) { Slog.w(TAG, "ExtraValue has two or more '='s"); } extraValueMap.put(pair[0], pair[1]); } } mExtraValueHashMapCache = extraValueMap; return extraValueMap; } } /** * The string of ExtraValue in subtype should be defined as follows: * example: key0,key1=value1,key2,key3,key4=value4 * @param key The key of extra value * @return The subtype contains specified the extra value */ public boolean containsExtraValueKey(String key) { return getExtraValueHashMap().containsKey(key); } /** * The string of ExtraValue in subtype should be defined as follows: * example: key0,key1=value1,key2,key3,key4=value4 * @param key The key of extra value * @return The value of the specified key */ public String getExtraValueOf(String key) { return getExtraValueHashMap().get(key); } @Override public int hashCode() { return mSubtypeHashCode; } /** * @hide * @return {@code true} if a valid subtype ID exists. */ public final boolean hasSubtypeId() { return mSubtypeId != SUBTYPE_ID_NONE; } /** * @hide * @return subtype ID. {@code 0} means that not subtype ID is specified. */ public final int getSubtypeId() { return mSubtypeId; } @Override public boolean equals(Object o) { if (o instanceof InputMethodSubtype) { InputMethodSubtype subtype = (InputMethodSubtype) o; if (subtype.mSubtypeId != 0 || mSubtypeId != 0) { return (subtype.hashCode() == hashCode()); } return (subtype.hashCode() == hashCode()) && (subtype.getLocale().equals(getLocale())) && (subtype.getLanguageTag().equals(getLanguageTag())) && (subtype.getMode().equals(getMode())) && (subtype.getExtraValue().equals(getExtraValue())) && (subtype.isAuxiliary() == isAuxiliary()) && (subtype.overridesImplicitlyEnabledSubtype() == overridesImplicitlyEnabledSubtype()) && (subtype.isAsciiCapable() == isAsciiCapable()); } return false; } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int parcelableFlags) { dest.writeInt(mSubtypeNameResId); dest.writeInt(mSubtypeIconResId); dest.writeString(mSubtypeLocale); dest.writeString(mSubtypeLanguageTag); dest.writeString(mSubtypeMode); dest.writeString(mSubtypeExtraValue); dest.writeInt(mIsAuxiliary ? 1 : 0); dest.writeInt(mOverridesImplicitlyEnabledSubtype ? 1 : 0); dest.writeInt(mSubtypeHashCode); dest.writeInt(mSubtypeId); dest.writeInt(mIsAsciiCapable ? 1 : 0); } public static final Parcelable.Creator<InputMethodSubtype> CREATOR = new Parcelable.Creator<InputMethodSubtype>() { @Override public InputMethodSubtype createFromParcel(Parcel source) { return new InputMethodSubtype(source); } @Override public InputMethodSubtype[] newArray(int size) { return new InputMethodSubtype[size]; } }; private static int hashCodeInternal(String locale, String mode, String extraValue, boolean isAuxiliary, boolean overridesImplicitlyEnabledSubtype, boolean isAsciiCapable) { // CAVEAT: Must revisit how to compute needsToCalculateCompatibleHashCode when a new // attribute is added in order to avoid enabled subtypes being unexpectedly disabled. final boolean needsToCalculateCompatibleHashCode = !isAsciiCapable; if (needsToCalculateCompatibleHashCode) { return Arrays.hashCode( new Object[] { locale, mode, extraValue, isAuxiliary, overridesImplicitlyEnabledSubtype }); } return Arrays.hashCode(new Object[] { locale, mode, extraValue, isAuxiliary, overridesImplicitlyEnabledSubtype, isAsciiCapable }); } /** * Sort the list of InputMethodSubtype * @param context Context will be used for getting localized strings from IME * @param flags Flags for the sort order * @param imi InputMethodInfo of which subtypes are subject to be sorted * @param subtypeList List of InputMethodSubtype which will be sorted * @return Sorted list of subtypes * @hide */ public static List<InputMethodSubtype> sort(Context context, int flags, InputMethodInfo imi, List<InputMethodSubtype> subtypeList) { if (imi == null) return subtypeList; final HashSet<InputMethodSubtype> inputSubtypesSet = new HashSet<InputMethodSubtype>(subtypeList); final ArrayList<InputMethodSubtype> sortedList = new ArrayList<InputMethodSubtype>(); int N = imi.getSubtypeCount(); for (int i = 0; i < N; ++i) { InputMethodSubtype subtype = imi.getSubtypeAt(i); if (inputSubtypesSet.contains(subtype)) { sortedList.add(subtype); inputSubtypesSet.remove(subtype); } } // If subtypes in inputSubtypesSet remain, that means these subtypes are not // contained in imi, so the remaining subtypes will be appended. for (InputMethodSubtype subtype : inputSubtypesSet) { sortedList.add(subtype); } return sortedList; } }