103b2ea1102d9e3e9f189173878706ab04533eea3satok/*
203b2ea1102d9e3e9f189173878706ab04533eea3satok * Copyright (C) 2011 The Android Open Source Project
303b2ea1102d9e3e9f189173878706ab04533eea3satok *
403b2ea1102d9e3e9f189173878706ab04533eea3satok * Licensed under the Apache License, Version 2.0 (the "License");
503b2ea1102d9e3e9f189173878706ab04533eea3satok * you may not use this file except in compliance with the License.
603b2ea1102d9e3e9f189173878706ab04533eea3satok * You may obtain a copy of the License at
703b2ea1102d9e3e9f189173878706ab04533eea3satok *
803b2ea1102d9e3e9f189173878706ab04533eea3satok *      http://www.apache.org/licenses/LICENSE-2.0
903b2ea1102d9e3e9f189173878706ab04533eea3satok *
1003b2ea1102d9e3e9f189173878706ab04533eea3satok * Unless required by applicable law or agreed to in writing, software
1103b2ea1102d9e3e9f189173878706ab04533eea3satok * distributed under the License is distributed on an "AS IS" BASIS,
1203b2ea1102d9e3e9f189173878706ab04533eea3satok * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1303b2ea1102d9e3e9f189173878706ab04533eea3satok * See the License for the specific language governing permissions and
1403b2ea1102d9e3e9f189173878706ab04533eea3satok * limitations under the License.
1503b2ea1102d9e3e9f189173878706ab04533eea3satok */
1603b2ea1102d9e3e9f189173878706ab04533eea3satok
1703b2ea1102d9e3e9f189173878706ab04533eea3satokpackage android.view.textservice;
1803b2ea1102d9e3e9f189173878706ab04533eea3satok
1903b2ea1102d9e3e9f189173878706ab04533eea3satokimport android.content.Context;
20c714531952fe1c22cae77631aa25dc7441b2b878satokimport android.content.pm.ApplicationInfo;
2103b2ea1102d9e3e9f189173878706ab04533eea3satokimport android.os.Parcel;
2203b2ea1102d9e3e9f189173878706ab04533eea3satokimport android.os.Parcelable;
23c714531952fe1c22cae77631aa25dc7441b2b878satokimport android.text.TextUtils;
240dc1f648a09b46c45190ba1ce7daecf7fada4347satokimport android.util.Slog;
2503b2ea1102d9e3e9f189173878706ab04533eea3satok
2603b2ea1102d9e3e9f189173878706ab04533eea3satokimport java.util.ArrayList;
2703b2ea1102d9e3e9f189173878706ab04533eea3satokimport java.util.Arrays;
280dc1f648a09b46c45190ba1ce7daecf7fada4347satokimport java.util.HashMap;
2903b2ea1102d9e3e9f189173878706ab04533eea3satokimport java.util.HashSet;
3003b2ea1102d9e3e9f189173878706ab04533eea3satokimport java.util.List;
31c714531952fe1c22cae77631aa25dc7441b2b878satokimport java.util.Locale;
3203b2ea1102d9e3e9f189173878706ab04533eea3satok
3303b2ea1102d9e3e9f189173878706ab04533eea3satok/**
3403b2ea1102d9e3e9f189173878706ab04533eea3satok * This class is used to specify meta information of a subtype contained in a spell checker.
3503b2ea1102d9e3e9f189173878706ab04533eea3satok * Subtype can describe locale (e.g. en_US, fr_FR...) used for settings.
3603b2ea1102d9e3e9f189173878706ab04533eea3satok */
3703b2ea1102d9e3e9f189173878706ab04533eea3satokpublic final class SpellCheckerSubtype implements Parcelable {
380dc1f648a09b46c45190ba1ce7daecf7fada4347satok    private static final String TAG = SpellCheckerSubtype.class.getSimpleName();
390dc1f648a09b46c45190ba1ce7daecf7fada4347satok    private static final String EXTRA_VALUE_PAIR_SEPARATOR = ",";
400dc1f648a09b46c45190ba1ce7daecf7fada4347satok    private static final String EXTRA_VALUE_KEY_VALUE_SEPARATOR = "=";
4103b2ea1102d9e3e9f189173878706ab04533eea3satok
4203b2ea1102d9e3e9f189173878706ab04533eea3satok    private final int mSubtypeHashCode;
4303b2ea1102d9e3e9f189173878706ab04533eea3satok    private final int mSubtypeNameResId;
4403b2ea1102d9e3e9f189173878706ab04533eea3satok    private final String mSubtypeLocale;
4503b2ea1102d9e3e9f189173878706ab04533eea3satok    private final String mSubtypeExtraValue;
460dc1f648a09b46c45190ba1ce7daecf7fada4347satok    private HashMap<String, String> mExtraValueHashMapCache;
4703b2ea1102d9e3e9f189173878706ab04533eea3satok
4803b2ea1102d9e3e9f189173878706ab04533eea3satok    /**
4903b2ea1102d9e3e9f189173878706ab04533eea3satok     * Constructor
5003b2ea1102d9e3e9f189173878706ab04533eea3satok     * @param nameId The name of the subtype
5103b2ea1102d9e3e9f189173878706ab04533eea3satok     * @param locale The locale supported by the subtype
5203b2ea1102d9e3e9f189173878706ab04533eea3satok     * @param extraValue The extra value of the subtype
5303b2ea1102d9e3e9f189173878706ab04533eea3satok     */
5403b2ea1102d9e3e9f189173878706ab04533eea3satok    public SpellCheckerSubtype(int nameId, String locale, String extraValue) {
5503b2ea1102d9e3e9f189173878706ab04533eea3satok        mSubtypeNameResId = nameId;
5603b2ea1102d9e3e9f189173878706ab04533eea3satok        mSubtypeLocale = locale != null ? locale : "";
5703b2ea1102d9e3e9f189173878706ab04533eea3satok        mSubtypeExtraValue = extraValue != null ? extraValue : "";
5803b2ea1102d9e3e9f189173878706ab04533eea3satok        mSubtypeHashCode = hashCodeInternal(mSubtypeLocale, mSubtypeExtraValue);
5903b2ea1102d9e3e9f189173878706ab04533eea3satok    }
6003b2ea1102d9e3e9f189173878706ab04533eea3satok
6103b2ea1102d9e3e9f189173878706ab04533eea3satok    SpellCheckerSubtype(Parcel source) {
6203b2ea1102d9e3e9f189173878706ab04533eea3satok        String s;
6303b2ea1102d9e3e9f189173878706ab04533eea3satok        mSubtypeNameResId = source.readInt();
6403b2ea1102d9e3e9f189173878706ab04533eea3satok        s = source.readString();
6503b2ea1102d9e3e9f189173878706ab04533eea3satok        mSubtypeLocale = s != null ? s : "";
6603b2ea1102d9e3e9f189173878706ab04533eea3satok        s = source.readString();
6703b2ea1102d9e3e9f189173878706ab04533eea3satok        mSubtypeExtraValue = s != null ? s : "";
6803b2ea1102d9e3e9f189173878706ab04533eea3satok        mSubtypeHashCode = hashCodeInternal(mSubtypeLocale, mSubtypeExtraValue);
6903b2ea1102d9e3e9f189173878706ab04533eea3satok    }
7003b2ea1102d9e3e9f189173878706ab04533eea3satok
7103b2ea1102d9e3e9f189173878706ab04533eea3satok    /**
7203b2ea1102d9e3e9f189173878706ab04533eea3satok     * @return the name of the subtype
7303b2ea1102d9e3e9f189173878706ab04533eea3satok     */
7403b2ea1102d9e3e9f189173878706ab04533eea3satok    public int getNameResId() {
7503b2ea1102d9e3e9f189173878706ab04533eea3satok        return mSubtypeNameResId;
7603b2ea1102d9e3e9f189173878706ab04533eea3satok    }
7703b2ea1102d9e3e9f189173878706ab04533eea3satok
7803b2ea1102d9e3e9f189173878706ab04533eea3satok    /**
7903b2ea1102d9e3e9f189173878706ab04533eea3satok     * @return the locale of the subtype
8003b2ea1102d9e3e9f189173878706ab04533eea3satok     */
8103b2ea1102d9e3e9f189173878706ab04533eea3satok    public String getLocale() {
8203b2ea1102d9e3e9f189173878706ab04533eea3satok        return mSubtypeLocale;
8303b2ea1102d9e3e9f189173878706ab04533eea3satok    }
8403b2ea1102d9e3e9f189173878706ab04533eea3satok
8503b2ea1102d9e3e9f189173878706ab04533eea3satok    /**
8603b2ea1102d9e3e9f189173878706ab04533eea3satok     * @return the extra value of the subtype
8703b2ea1102d9e3e9f189173878706ab04533eea3satok     */
8803b2ea1102d9e3e9f189173878706ab04533eea3satok    public String getExtraValue() {
8903b2ea1102d9e3e9f189173878706ab04533eea3satok        return mSubtypeExtraValue;
9003b2ea1102d9e3e9f189173878706ab04533eea3satok    }
9103b2ea1102d9e3e9f189173878706ab04533eea3satok
920dc1f648a09b46c45190ba1ce7daecf7fada4347satok    private HashMap<String, String> getExtraValueHashMap() {
930dc1f648a09b46c45190ba1ce7daecf7fada4347satok        if (mExtraValueHashMapCache == null) {
940dc1f648a09b46c45190ba1ce7daecf7fada4347satok            mExtraValueHashMapCache = new HashMap<String, String>();
950dc1f648a09b46c45190ba1ce7daecf7fada4347satok            final String[] pairs = mSubtypeExtraValue.split(EXTRA_VALUE_PAIR_SEPARATOR);
960dc1f648a09b46c45190ba1ce7daecf7fada4347satok            final int N = pairs.length;
970dc1f648a09b46c45190ba1ce7daecf7fada4347satok            for (int i = 0; i < N; ++i) {
980dc1f648a09b46c45190ba1ce7daecf7fada4347satok                final String[] pair = pairs[i].split(EXTRA_VALUE_KEY_VALUE_SEPARATOR);
990dc1f648a09b46c45190ba1ce7daecf7fada4347satok                if (pair.length == 1) {
1000dc1f648a09b46c45190ba1ce7daecf7fada4347satok                    mExtraValueHashMapCache.put(pair[0], null);
1010dc1f648a09b46c45190ba1ce7daecf7fada4347satok                } else if (pair.length > 1) {
1020dc1f648a09b46c45190ba1ce7daecf7fada4347satok                    if (pair.length > 2) {
1030dc1f648a09b46c45190ba1ce7daecf7fada4347satok                        Slog.w(TAG, "ExtraValue has two or more '='s");
1040dc1f648a09b46c45190ba1ce7daecf7fada4347satok                    }
1050dc1f648a09b46c45190ba1ce7daecf7fada4347satok                    mExtraValueHashMapCache.put(pair[0], pair[1]);
1060dc1f648a09b46c45190ba1ce7daecf7fada4347satok                }
1070dc1f648a09b46c45190ba1ce7daecf7fada4347satok            }
1080dc1f648a09b46c45190ba1ce7daecf7fada4347satok        }
1090dc1f648a09b46c45190ba1ce7daecf7fada4347satok        return mExtraValueHashMapCache;
1100dc1f648a09b46c45190ba1ce7daecf7fada4347satok    }
1110dc1f648a09b46c45190ba1ce7daecf7fada4347satok
1120dc1f648a09b46c45190ba1ce7daecf7fada4347satok    /**
1130dc1f648a09b46c45190ba1ce7daecf7fada4347satok     * The string of ExtraValue in subtype should be defined as follows:
1140dc1f648a09b46c45190ba1ce7daecf7fada4347satok     * example: key0,key1=value1,key2,key3,key4=value4
1150dc1f648a09b46c45190ba1ce7daecf7fada4347satok     * @param key the key of extra value
1160dc1f648a09b46c45190ba1ce7daecf7fada4347satok     * @return the subtype contains specified the extra value
1170dc1f648a09b46c45190ba1ce7daecf7fada4347satok     */
1180dc1f648a09b46c45190ba1ce7daecf7fada4347satok    public boolean containsExtraValueKey(String key) {
1190dc1f648a09b46c45190ba1ce7daecf7fada4347satok        return getExtraValueHashMap().containsKey(key);
1200dc1f648a09b46c45190ba1ce7daecf7fada4347satok    }
1210dc1f648a09b46c45190ba1ce7daecf7fada4347satok
1220dc1f648a09b46c45190ba1ce7daecf7fada4347satok    /**
1230dc1f648a09b46c45190ba1ce7daecf7fada4347satok     * The string of ExtraValue in subtype should be defined as follows:
1240dc1f648a09b46c45190ba1ce7daecf7fada4347satok     * example: key0,key1=value1,key2,key3,key4=value4
1250dc1f648a09b46c45190ba1ce7daecf7fada4347satok     * @param key the key of extra value
1260dc1f648a09b46c45190ba1ce7daecf7fada4347satok     * @return the value of the specified key
1270dc1f648a09b46c45190ba1ce7daecf7fada4347satok     */
1280dc1f648a09b46c45190ba1ce7daecf7fada4347satok    public String getExtraValueOf(String key) {
1290dc1f648a09b46c45190ba1ce7daecf7fada4347satok        return getExtraValueHashMap().get(key);
1300dc1f648a09b46c45190ba1ce7daecf7fada4347satok    }
1310dc1f648a09b46c45190ba1ce7daecf7fada4347satok
13203b2ea1102d9e3e9f189173878706ab04533eea3satok    @Override
13303b2ea1102d9e3e9f189173878706ab04533eea3satok    public int hashCode() {
13403b2ea1102d9e3e9f189173878706ab04533eea3satok        return mSubtypeHashCode;
13503b2ea1102d9e3e9f189173878706ab04533eea3satok    }
13603b2ea1102d9e3e9f189173878706ab04533eea3satok
13703b2ea1102d9e3e9f189173878706ab04533eea3satok    @Override
13803b2ea1102d9e3e9f189173878706ab04533eea3satok    public boolean equals(Object o) {
13903b2ea1102d9e3e9f189173878706ab04533eea3satok        if (o instanceof SpellCheckerSubtype) {
14003b2ea1102d9e3e9f189173878706ab04533eea3satok            SpellCheckerSubtype subtype = (SpellCheckerSubtype) o;
14103b2ea1102d9e3e9f189173878706ab04533eea3satok            return (subtype.hashCode() == hashCode())
14203b2ea1102d9e3e9f189173878706ab04533eea3satok                && (subtype.getNameResId() == getNameResId())
14303b2ea1102d9e3e9f189173878706ab04533eea3satok                && (subtype.getLocale().equals(getLocale()))
14403b2ea1102d9e3e9f189173878706ab04533eea3satok                && (subtype.getExtraValue().equals(getExtraValue()));
14503b2ea1102d9e3e9f189173878706ab04533eea3satok        }
14603b2ea1102d9e3e9f189173878706ab04533eea3satok        return false;
14703b2ea1102d9e3e9f189173878706ab04533eea3satok    }
14803b2ea1102d9e3e9f189173878706ab04533eea3satok
149f927e17ae543b6edeae8200cc86c59c3ee740670satok    /**
150f927e17ae543b6edeae8200cc86c59c3ee740670satok     * @hide
151f927e17ae543b6edeae8200cc86c59c3ee740670satok     */
152f927e17ae543b6edeae8200cc86c59c3ee740670satok    public static Locale constructLocaleFromString(String localeStr) {
153c714531952fe1c22cae77631aa25dc7441b2b878satok        if (TextUtils.isEmpty(localeStr))
154c714531952fe1c22cae77631aa25dc7441b2b878satok            return null;
155c714531952fe1c22cae77631aa25dc7441b2b878satok        String[] localeParams = localeStr.split("_", 3);
156c714531952fe1c22cae77631aa25dc7441b2b878satok        // The length of localeStr is guaranteed to always return a 1 <= value <= 3
157c714531952fe1c22cae77631aa25dc7441b2b878satok        // because localeStr is not empty.
158c714531952fe1c22cae77631aa25dc7441b2b878satok        if (localeParams.length == 1) {
159c714531952fe1c22cae77631aa25dc7441b2b878satok            return new Locale(localeParams[0]);
160c714531952fe1c22cae77631aa25dc7441b2b878satok        } else if (localeParams.length == 2) {
161c714531952fe1c22cae77631aa25dc7441b2b878satok            return new Locale(localeParams[0], localeParams[1]);
162c714531952fe1c22cae77631aa25dc7441b2b878satok        } else if (localeParams.length == 3) {
163c714531952fe1c22cae77631aa25dc7441b2b878satok            return new Locale(localeParams[0], localeParams[1], localeParams[2]);
164c714531952fe1c22cae77631aa25dc7441b2b878satok        }
165c714531952fe1c22cae77631aa25dc7441b2b878satok        return null;
166c714531952fe1c22cae77631aa25dc7441b2b878satok    }
167c714531952fe1c22cae77631aa25dc7441b2b878satok
168c714531952fe1c22cae77631aa25dc7441b2b878satok    /**
169c714531952fe1c22cae77631aa25dc7441b2b878satok     * @param context Context will be used for getting Locale and PackageManager.
170c714531952fe1c22cae77631aa25dc7441b2b878satok     * @param packageName The package name of the spell checker
171c714531952fe1c22cae77631aa25dc7441b2b878satok     * @param appInfo The application info of the spell checker
172c714531952fe1c22cae77631aa25dc7441b2b878satok     * @return a display name for this subtype. The string resource of the label (mSubtypeNameResId)
173c714531952fe1c22cae77631aa25dc7441b2b878satok     * can have only one %s in it. If there is, the %s part will be replaced with the locale's
174c714531952fe1c22cae77631aa25dc7441b2b878satok     * display name by the formatter. If there is not, this method simply returns the string
175c714531952fe1c22cae77631aa25dc7441b2b878satok     * specified by mSubtypeNameResId. If mSubtypeNameResId is not specified (== 0), it's up to the
176c714531952fe1c22cae77631aa25dc7441b2b878satok     * framework to generate an appropriate display name.
177c714531952fe1c22cae77631aa25dc7441b2b878satok     */
178c714531952fe1c22cae77631aa25dc7441b2b878satok    public CharSequence getDisplayName(
179c714531952fe1c22cae77631aa25dc7441b2b878satok            Context context, String packageName, ApplicationInfo appInfo) {
180c714531952fe1c22cae77631aa25dc7441b2b878satok        final Locale locale = constructLocaleFromString(mSubtypeLocale);
181c714531952fe1c22cae77631aa25dc7441b2b878satok        final String localeStr = locale != null ? locale.getDisplayName() : mSubtypeLocale;
182c714531952fe1c22cae77631aa25dc7441b2b878satok        if (mSubtypeNameResId == 0) {
183c714531952fe1c22cae77631aa25dc7441b2b878satok            return localeStr;
184c714531952fe1c22cae77631aa25dc7441b2b878satok        }
185c714531952fe1c22cae77631aa25dc7441b2b878satok        final CharSequence subtypeName = context.getPackageManager().getText(
186c714531952fe1c22cae77631aa25dc7441b2b878satok                packageName, mSubtypeNameResId, appInfo);
187c714531952fe1c22cae77631aa25dc7441b2b878satok        if (!TextUtils.isEmpty(subtypeName)) {
188c714531952fe1c22cae77631aa25dc7441b2b878satok            return String.format(subtypeName.toString(), localeStr);
189c714531952fe1c22cae77631aa25dc7441b2b878satok        } else {
190c714531952fe1c22cae77631aa25dc7441b2b878satok            return localeStr;
191c714531952fe1c22cae77631aa25dc7441b2b878satok        }
192c714531952fe1c22cae77631aa25dc7441b2b878satok    }
193c714531952fe1c22cae77631aa25dc7441b2b878satok
19403b2ea1102d9e3e9f189173878706ab04533eea3satok    @Override
19503b2ea1102d9e3e9f189173878706ab04533eea3satok    public int describeContents() {
19603b2ea1102d9e3e9f189173878706ab04533eea3satok        return 0;
19703b2ea1102d9e3e9f189173878706ab04533eea3satok    }
19803b2ea1102d9e3e9f189173878706ab04533eea3satok
19903b2ea1102d9e3e9f189173878706ab04533eea3satok    @Override
20003b2ea1102d9e3e9f189173878706ab04533eea3satok    public void writeToParcel(Parcel dest, int parcelableFlags) {
20103b2ea1102d9e3e9f189173878706ab04533eea3satok        dest.writeInt(mSubtypeNameResId);
20203b2ea1102d9e3e9f189173878706ab04533eea3satok        dest.writeString(mSubtypeLocale);
20303b2ea1102d9e3e9f189173878706ab04533eea3satok        dest.writeString(mSubtypeExtraValue);
20403b2ea1102d9e3e9f189173878706ab04533eea3satok    }
20503b2ea1102d9e3e9f189173878706ab04533eea3satok
20603b2ea1102d9e3e9f189173878706ab04533eea3satok    public static final Parcelable.Creator<SpellCheckerSubtype> CREATOR
20703b2ea1102d9e3e9f189173878706ab04533eea3satok            = new Parcelable.Creator<SpellCheckerSubtype>() {
20803b2ea1102d9e3e9f189173878706ab04533eea3satok        @Override
20903b2ea1102d9e3e9f189173878706ab04533eea3satok        public SpellCheckerSubtype createFromParcel(Parcel source) {
21003b2ea1102d9e3e9f189173878706ab04533eea3satok            return new SpellCheckerSubtype(source);
21103b2ea1102d9e3e9f189173878706ab04533eea3satok        }
21203b2ea1102d9e3e9f189173878706ab04533eea3satok
21303b2ea1102d9e3e9f189173878706ab04533eea3satok        @Override
21403b2ea1102d9e3e9f189173878706ab04533eea3satok        public SpellCheckerSubtype[] newArray(int size) {
21503b2ea1102d9e3e9f189173878706ab04533eea3satok            return new SpellCheckerSubtype[size];
21603b2ea1102d9e3e9f189173878706ab04533eea3satok        }
21703b2ea1102d9e3e9f189173878706ab04533eea3satok    };
21803b2ea1102d9e3e9f189173878706ab04533eea3satok
21903b2ea1102d9e3e9f189173878706ab04533eea3satok    private static int hashCodeInternal(String locale, String extraValue) {
22003b2ea1102d9e3e9f189173878706ab04533eea3satok        return Arrays.hashCode(new Object[] {locale, extraValue});
22103b2ea1102d9e3e9f189173878706ab04533eea3satok    }
22203b2ea1102d9e3e9f189173878706ab04533eea3satok
22303b2ea1102d9e3e9f189173878706ab04533eea3satok    /**
22403b2ea1102d9e3e9f189173878706ab04533eea3satok     * Sort the list of subtypes
22503b2ea1102d9e3e9f189173878706ab04533eea3satok     * @param context Context will be used for getting localized strings
22603b2ea1102d9e3e9f189173878706ab04533eea3satok     * @param flags Flags for the sort order
22703b2ea1102d9e3e9f189173878706ab04533eea3satok     * @param sci SpellCheckerInfo of which subtypes are subject to be sorted
22803b2ea1102d9e3e9f189173878706ab04533eea3satok     * @param subtypeList List which will be sorted
22903b2ea1102d9e3e9f189173878706ab04533eea3satok     * @return Sorted list of subtypes
23003b2ea1102d9e3e9f189173878706ab04533eea3satok     * @hide
23103b2ea1102d9e3e9f189173878706ab04533eea3satok     */
23203b2ea1102d9e3e9f189173878706ab04533eea3satok    public static List<SpellCheckerSubtype> sort(Context context, int flags, SpellCheckerInfo sci,
23303b2ea1102d9e3e9f189173878706ab04533eea3satok            List<SpellCheckerSubtype> subtypeList) {
23403b2ea1102d9e3e9f189173878706ab04533eea3satok        if (sci == null) return subtypeList;
23503b2ea1102d9e3e9f189173878706ab04533eea3satok        final HashSet<SpellCheckerSubtype> subtypesSet = new HashSet<SpellCheckerSubtype>(
23603b2ea1102d9e3e9f189173878706ab04533eea3satok                subtypeList);
23703b2ea1102d9e3e9f189173878706ab04533eea3satok        final ArrayList<SpellCheckerSubtype> sortedList = new ArrayList<SpellCheckerSubtype>();
23803b2ea1102d9e3e9f189173878706ab04533eea3satok        int N = sci.getSubtypeCount();
23903b2ea1102d9e3e9f189173878706ab04533eea3satok        for (int i = 0; i < N; ++i) {
24003b2ea1102d9e3e9f189173878706ab04533eea3satok            SpellCheckerSubtype subtype = sci.getSubtypeAt(i);
24103b2ea1102d9e3e9f189173878706ab04533eea3satok            if (subtypesSet.contains(subtype)) {
24203b2ea1102d9e3e9f189173878706ab04533eea3satok                sortedList.add(subtype);
24303b2ea1102d9e3e9f189173878706ab04533eea3satok                subtypesSet.remove(subtype);
24403b2ea1102d9e3e9f189173878706ab04533eea3satok            }
24503b2ea1102d9e3e9f189173878706ab04533eea3satok        }
24603b2ea1102d9e3e9f189173878706ab04533eea3satok        // If subtypes in subtypesSet remain, that means these subtypes are not
24703b2ea1102d9e3e9f189173878706ab04533eea3satok        // contained in sci, so the remaining subtypes will be appended.
24803b2ea1102d9e3e9f189173878706ab04533eea3satok        for (SpellCheckerSubtype subtype: subtypesSet) {
24903b2ea1102d9e3e9f189173878706ab04533eea3satok            sortedList.add(subtype);
25003b2ea1102d9e3e9f189173878706ab04533eea3satok        }
25103b2ea1102d9e3e9f189173878706ab04533eea3satok        return sortedList;
25203b2ea1102d9e3e9f189173878706ab04533eea3satok    }
25303b2ea1102d9e3e9f189173878706ab04533eea3satok}
254