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