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