1/*
2 * Copyright (C) 2011 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.textservice;
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.List;
31import java.util.Locale;
32
33/**
34 * This class is used to specify meta information of a subtype contained in a spell checker.
35 * Subtype can describe locale (e.g. en_US, fr_FR...) used for settings.
36 */
37public final class SpellCheckerSubtype implements Parcelable {
38    private static final String TAG = SpellCheckerSubtype.class.getSimpleName();
39    private static final String EXTRA_VALUE_PAIR_SEPARATOR = ",";
40    private static final String EXTRA_VALUE_KEY_VALUE_SEPARATOR = "=";
41
42    private final int mSubtypeHashCode;
43    private final int mSubtypeNameResId;
44    private final String mSubtypeLocale;
45    private final String mSubtypeExtraValue;
46    private HashMap<String, String> mExtraValueHashMapCache;
47
48    /**
49     * Constructor
50     * @param nameId The name of the subtype
51     * @param locale The locale supported by the subtype
52     * @param extraValue The extra value of the subtype
53     */
54    public SpellCheckerSubtype(int nameId, String locale, String extraValue) {
55        mSubtypeNameResId = nameId;
56        mSubtypeLocale = locale != null ? locale : "";
57        mSubtypeExtraValue = extraValue != null ? extraValue : "";
58        mSubtypeHashCode = hashCodeInternal(mSubtypeLocale, mSubtypeExtraValue);
59    }
60
61    SpellCheckerSubtype(Parcel source) {
62        String s;
63        mSubtypeNameResId = source.readInt();
64        s = source.readString();
65        mSubtypeLocale = s != null ? s : "";
66        s = source.readString();
67        mSubtypeExtraValue = s != null ? s : "";
68        mSubtypeHashCode = hashCodeInternal(mSubtypeLocale, mSubtypeExtraValue);
69    }
70
71    /**
72     * @return the name of the subtype
73     */
74    public int getNameResId() {
75        return mSubtypeNameResId;
76    }
77
78    /**
79     * @return the locale of the subtype
80     */
81    public String getLocale() {
82        return mSubtypeLocale;
83    }
84
85    /**
86     * @return the extra value of the subtype
87     */
88    public String getExtraValue() {
89        return mSubtypeExtraValue;
90    }
91
92    private HashMap<String, String> getExtraValueHashMap() {
93        if (mExtraValueHashMapCache == null) {
94            mExtraValueHashMapCache = new HashMap<String, String>();
95            final String[] pairs = mSubtypeExtraValue.split(EXTRA_VALUE_PAIR_SEPARATOR);
96            final int N = pairs.length;
97            for (int i = 0; i < N; ++i) {
98                final String[] pair = pairs[i].split(EXTRA_VALUE_KEY_VALUE_SEPARATOR);
99                if (pair.length == 1) {
100                    mExtraValueHashMapCache.put(pair[0], null);
101                } else if (pair.length > 1) {
102                    if (pair.length > 2) {
103                        Slog.w(TAG, "ExtraValue has two or more '='s");
104                    }
105                    mExtraValueHashMapCache.put(pair[0], pair[1]);
106                }
107            }
108        }
109        return mExtraValueHashMapCache;
110    }
111
112    /**
113     * The string of ExtraValue in subtype should be defined as follows:
114     * example: key0,key1=value1,key2,key3,key4=value4
115     * @param key the key of extra value
116     * @return the subtype contains specified the extra value
117     */
118    public boolean containsExtraValueKey(String key) {
119        return getExtraValueHashMap().containsKey(key);
120    }
121
122    /**
123     * The string of ExtraValue in subtype should be defined as follows:
124     * example: key0,key1=value1,key2,key3,key4=value4
125     * @param key the key of extra value
126     * @return the value of the specified key
127     */
128    public String getExtraValueOf(String key) {
129        return getExtraValueHashMap().get(key);
130    }
131
132    @Override
133    public int hashCode() {
134        return mSubtypeHashCode;
135    }
136
137    @Override
138    public boolean equals(Object o) {
139        if (o instanceof SpellCheckerSubtype) {
140            SpellCheckerSubtype subtype = (SpellCheckerSubtype) o;
141            return (subtype.hashCode() == hashCode())
142                && (subtype.getNameResId() == getNameResId())
143                && (subtype.getLocale().equals(getLocale()))
144                && (subtype.getExtraValue().equals(getExtraValue()));
145        }
146        return false;
147    }
148
149    /**
150     * @hide
151     */
152    public static Locale constructLocaleFromString(String localeStr) {
153        if (TextUtils.isEmpty(localeStr))
154            return null;
155        String[] localeParams = localeStr.split("_", 3);
156        // The length of localeStr is guaranteed to always return a 1 <= value <= 3
157        // because localeStr is not empty.
158        if (localeParams.length == 1) {
159            return new Locale(localeParams[0]);
160        } else if (localeParams.length == 2) {
161            return new Locale(localeParams[0], localeParams[1]);
162        } else if (localeParams.length == 3) {
163            return new Locale(localeParams[0], localeParams[1], localeParams[2]);
164        }
165        return null;
166    }
167
168    /**
169     * @param context Context will be used for getting Locale and PackageManager.
170     * @param packageName The package name of the spell checker
171     * @param appInfo The application info of the spell checker
172     * @return a display name for this subtype. The string resource of the label (mSubtypeNameResId)
173     * can have only one %s in it. If there is, the %s part will be replaced with the locale's
174     * display name by the formatter. If there is not, this method simply returns the string
175     * specified by mSubtypeNameResId. If mSubtypeNameResId is not specified (== 0), it's up to the
176     * framework to generate an appropriate display name.
177     */
178    public CharSequence getDisplayName(
179            Context context, String packageName, ApplicationInfo appInfo) {
180        final Locale locale = constructLocaleFromString(mSubtypeLocale);
181        final String localeStr = locale != null ? locale.getDisplayName() : mSubtypeLocale;
182        if (mSubtypeNameResId == 0) {
183            return localeStr;
184        }
185        final CharSequence subtypeName = context.getPackageManager().getText(
186                packageName, mSubtypeNameResId, appInfo);
187        if (!TextUtils.isEmpty(subtypeName)) {
188            return String.format(subtypeName.toString(), localeStr);
189        } else {
190            return localeStr;
191        }
192    }
193
194    @Override
195    public int describeContents() {
196        return 0;
197    }
198
199    @Override
200    public void writeToParcel(Parcel dest, int parcelableFlags) {
201        dest.writeInt(mSubtypeNameResId);
202        dest.writeString(mSubtypeLocale);
203        dest.writeString(mSubtypeExtraValue);
204    }
205
206    public static final Parcelable.Creator<SpellCheckerSubtype> CREATOR
207            = new Parcelable.Creator<SpellCheckerSubtype>() {
208        @Override
209        public SpellCheckerSubtype createFromParcel(Parcel source) {
210            return new SpellCheckerSubtype(source);
211        }
212
213        @Override
214        public SpellCheckerSubtype[] newArray(int size) {
215            return new SpellCheckerSubtype[size];
216        }
217    };
218
219    private static int hashCodeInternal(String locale, String extraValue) {
220        return Arrays.hashCode(new Object[] {locale, extraValue});
221    }
222
223    /**
224     * Sort the list of subtypes
225     * @param context Context will be used for getting localized strings
226     * @param flags Flags for the sort order
227     * @param sci SpellCheckerInfo of which subtypes are subject to be sorted
228     * @param subtypeList List which will be sorted
229     * @return Sorted list of subtypes
230     * @hide
231     */
232    public static List<SpellCheckerSubtype> sort(Context context, int flags, SpellCheckerInfo sci,
233            List<SpellCheckerSubtype> subtypeList) {
234        if (sci == null) return subtypeList;
235        final HashSet<SpellCheckerSubtype> subtypesSet = new HashSet<SpellCheckerSubtype>(
236                subtypeList);
237        final ArrayList<SpellCheckerSubtype> sortedList = new ArrayList<SpellCheckerSubtype>();
238        int N = sci.getSubtypeCount();
239        for (int i = 0; i < N; ++i) {
240            SpellCheckerSubtype subtype = sci.getSubtypeAt(i);
241            if (subtypesSet.contains(subtype)) {
242                sortedList.add(subtype);
243                subtypesSet.remove(subtype);
244            }
245        }
246        // If subtypes in subtypesSet remain, that means these subtypes are not
247        // contained in sci, so the remaining subtypes will be appended.
248        for (SpellCheckerSubtype subtype: subtypesSet) {
249            sortedList.add(subtype);
250        }
251        return sortedList;
252    }
253}
254