InputMethodInfo.java revision f1367b7e903a2a69a8f833bb272e91d77abd57c6
1/*
2 * Copyright (C) 2007-2008 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 * use this file except in compliance with the License. You may obtain a copy of
6 * 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, WITHOUT
12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 * License for the specific language governing permissions and limitations under
14 * the License.
15 */
16
17package android.view.inputmethod;
18
19import org.xmlpull.v1.XmlPullParser;
20import org.xmlpull.v1.XmlPullParserException;
21
22import android.content.ComponentName;
23import android.content.Context;
24import android.content.pm.ApplicationInfo;
25import android.content.pm.PackageManager;
26import android.content.pm.PackageManager.NameNotFoundException;
27import android.content.pm.ResolveInfo;
28import android.content.pm.ServiceInfo;
29import android.content.res.Resources;
30import android.content.res.TypedArray;
31import android.content.res.XmlResourceParser;
32import android.graphics.drawable.Drawable;
33import android.os.Parcel;
34import android.os.Parcelable;
35import android.util.AttributeSet;
36import android.util.Printer;
37import android.util.Slog;
38import android.util.Xml;
39
40import java.io.IOException;
41import java.util.ArrayList;
42import java.util.List;
43import java.util.Map;
44
45/**
46 * This class is used to specify meta information of an input method.
47 */
48public final class InputMethodInfo implements Parcelable {
49    static final String TAG = "InputMethodInfo";
50
51    /**
52     * The Service that implements this input method component.
53     */
54    final ResolveInfo mService;
55
56    /**
57     * The unique string Id to identify the input method.  This is generated
58     * from the input method component.
59     */
60    final String mId;
61
62    /**
63     * The input method setting activity's name, used by the system settings to
64     * launch the setting activity of this input method.
65     */
66    final String mSettingsActivityName;
67
68    /**
69     * The resource in the input method's .apk that holds a boolean indicating
70     * whether it should be considered the default input method for this
71     * system.  This is a resource ID instead of the final value so that it
72     * can change based on the configuration (in particular locale).
73     */
74    final int mIsDefaultResId;
75
76    /**
77     * The array of the subtypes.
78     */
79    private final ArrayList<InputMethodSubtype> mSubtypes = new ArrayList<InputMethodSubtype>();
80
81    private boolean mIsAuxIme;
82
83    /**
84     * Cavert: mForceDefault must be false for production. This flag is only for test.
85     */
86    private final boolean mForceDefault;
87
88    /**
89     * Constructor.
90     *
91     * @param context The Context in which we are parsing the input method.
92     * @param service The ResolveInfo returned from the package manager about
93     * this input method's component.
94     */
95    public InputMethodInfo(Context context, ResolveInfo service)
96            throws XmlPullParserException, IOException {
97        this(context, service, null);
98    }
99
100    /**
101     * Constructor.
102     *
103     * @param context The Context in which we are parsing the input method.
104     * @param service The ResolveInfo returned from the package manager about
105     * this input method's component.
106     * @param additionalSubtypes additional subtypes being added to this InputMethodInfo
107     * @hide
108     */
109    public InputMethodInfo(Context context, ResolveInfo service,
110            Map<String, List<InputMethodSubtype>> additionalSubtypesMap)
111            throws XmlPullParserException, IOException {
112        mService = service;
113        ServiceInfo si = service.serviceInfo;
114        mId = new ComponentName(si.packageName, si.name).flattenToShortString();
115        mIsAuxIme = true;
116        mForceDefault = false;
117
118        PackageManager pm = context.getPackageManager();
119        String settingsActivityComponent = null;
120        int isDefaultResId = 0;
121
122        XmlResourceParser parser = null;
123        try {
124            parser = si.loadXmlMetaData(pm, InputMethod.SERVICE_META_DATA);
125            if (parser == null) {
126                throw new XmlPullParserException("No "
127                        + InputMethod.SERVICE_META_DATA + " meta-data");
128            }
129
130            Resources res = pm.getResourcesForApplication(si.applicationInfo);
131
132            AttributeSet attrs = Xml.asAttributeSet(parser);
133
134            int type;
135            while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
136                    && type != XmlPullParser.START_TAG) {
137            }
138
139            String nodeName = parser.getName();
140            if (!"input-method".equals(nodeName)) {
141                throw new XmlPullParserException(
142                        "Meta-data does not start with input-method tag");
143            }
144
145            TypedArray sa = res.obtainAttributes(attrs,
146                    com.android.internal.R.styleable.InputMethod);
147            settingsActivityComponent = sa.getString(
148                    com.android.internal.R.styleable.InputMethod_settingsActivity);
149            isDefaultResId = sa.getResourceId(
150                    com.android.internal.R.styleable.InputMethod_isDefault, 0);
151            sa.recycle();
152
153            final int depth = parser.getDepth();
154            // Parse all subtypes
155            while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth)
156                    && type != XmlPullParser.END_DOCUMENT) {
157                if (type == XmlPullParser.START_TAG) {
158                    nodeName = parser.getName();
159                    if (!"subtype".equals(nodeName)) {
160                        throw new XmlPullParserException(
161                                "Meta-data in input-method does not start with subtype tag");
162                    }
163                    final TypedArray a = res.obtainAttributes(
164                            attrs, com.android.internal.R.styleable.InputMethod_Subtype);
165                    InputMethodSubtype subtype = new InputMethodSubtype(
166                            a.getResourceId(com.android.internal.R.styleable
167                                    .InputMethod_Subtype_label, 0),
168                            a.getResourceId(com.android.internal.R.styleable
169                                    .InputMethod_Subtype_icon, 0),
170                            a.getString(com.android.internal.R.styleable
171                                    .InputMethod_Subtype_imeSubtypeLocale),
172                            a.getString(com.android.internal.R.styleable
173                                    .InputMethod_Subtype_imeSubtypeMode),
174                            a.getString(com.android.internal.R.styleable
175                                    .InputMethod_Subtype_imeSubtypeExtraValue),
176                            a.getBoolean(com.android.internal.R.styleable
177                                    .InputMethod_Subtype_isAuxiliary, false),
178                            a.getBoolean(com.android.internal.R.styleable
179                                    .InputMethod_Subtype_overridesImplicitlyEnabledSubtype, false),
180                            a.getInt(com.android.internal.R.styleable
181                                    .InputMethod_Subtype_subtypeId, 0 /* use Arrays.hashCode */)
182                            );
183                    if (!subtype.isAuxiliary()) {
184                        mIsAuxIme = false;
185                    }
186                    mSubtypes.add(subtype);
187                }
188            }
189        } catch (NameNotFoundException e) {
190            throw new XmlPullParserException(
191                    "Unable to create context for: " + si.packageName);
192        } finally {
193            if (parser != null) parser.close();
194        }
195
196        if (mSubtypes.size() == 0) {
197            mIsAuxIme = false;
198        }
199
200        if (additionalSubtypesMap != null && additionalSubtypesMap.containsKey(mId)) {
201            final List<InputMethodSubtype> additionalSubtypes = additionalSubtypesMap.get(mId);
202            final int N = additionalSubtypes.size();
203            for (int i = 0; i < N; ++i) {
204                final InputMethodSubtype subtype = additionalSubtypes.get(i);
205                if (!mSubtypes.contains(subtype)) {
206                    mSubtypes.add(subtype);
207                } else {
208                    Slog.w(TAG, "Duplicated subtype definition found: "
209                            + subtype.getLocale() + ", " + subtype.getMode());
210                }
211            }
212        }
213        mSettingsActivityName = settingsActivityComponent;
214        mIsDefaultResId = isDefaultResId;
215    }
216
217    InputMethodInfo(Parcel source) {
218        mId = source.readString();
219        mSettingsActivityName = source.readString();
220        mIsDefaultResId = source.readInt();
221        mIsAuxIme = source.readInt() == 1;
222        mService = ResolveInfo.CREATOR.createFromParcel(source);
223        source.readTypedList(mSubtypes, InputMethodSubtype.CREATOR);
224        mForceDefault = false;
225    }
226
227    /**
228     * Temporary API for creating a built-in input method for test.
229     */
230    public InputMethodInfo(String packageName, String className,
231            CharSequence label, String settingsActivity) {
232        this(buildDummyResolveInfo(packageName, className, label), false, settingsActivity, null,
233                0, false);
234    }
235
236    /**
237     * Temporary API for creating a built-in input method for test.
238     * @hide
239     */
240    public InputMethodInfo(ResolveInfo ri, boolean isAuxIme,
241            String settingsActivity, List<InputMethodSubtype> subtypes, int isDefaultResId,
242            boolean forceDefault) {
243        final ServiceInfo si = ri.serviceInfo;
244        mService = ri;
245        mId = new ComponentName(si.packageName, si.name).flattenToShortString();
246        mSettingsActivityName = settingsActivity;
247        mIsDefaultResId = isDefaultResId;
248        mIsAuxIme = isAuxIme;
249        if (subtypes != null) {
250            mSubtypes.addAll(subtypes);
251        }
252        mForceDefault = forceDefault;
253    }
254
255    private static ResolveInfo buildDummyResolveInfo(String packageName, String className,
256            CharSequence label) {
257        ResolveInfo ri = new ResolveInfo();
258        ServiceInfo si = new ServiceInfo();
259        ApplicationInfo ai = new ApplicationInfo();
260        ai.packageName = packageName;
261        ai.enabled = true;
262        si.applicationInfo = ai;
263        si.enabled = true;
264        si.packageName = packageName;
265        si.name = className;
266        si.exported = true;
267        si.nonLocalizedLabel = label;
268        ri.serviceInfo = si;
269        return ri;
270    }
271
272    /**
273     * Return a unique ID for this input method.  The ID is generated from
274     * the package and class name implementing the method.
275     */
276    public String getId() {
277        return mId;
278    }
279
280    /**
281     * Return the .apk package that implements this input method.
282     */
283    public String getPackageName() {
284        return mService.serviceInfo.packageName;
285    }
286
287    /**
288     * Return the class name of the service component that implements
289     * this input method.
290     */
291    public String getServiceName() {
292        return mService.serviceInfo.name;
293    }
294
295    /**
296     * Return the raw information about the Service implementing this
297     * input method.  Do not modify the returned object.
298     */
299    public ServiceInfo getServiceInfo() {
300        return mService.serviceInfo;
301    }
302
303    /**
304     * Return the component of the service that implements this input
305     * method.
306     */
307    public ComponentName getComponent() {
308        return new ComponentName(mService.serviceInfo.packageName,
309                mService.serviceInfo.name);
310    }
311
312    /**
313     * Load the user-displayed label for this input method.
314     *
315     * @param pm Supply a PackageManager used to load the input method's
316     * resources.
317     */
318    public CharSequence loadLabel(PackageManager pm) {
319        return mService.loadLabel(pm);
320    }
321
322    /**
323     * Load the user-displayed icon for this input method.
324     *
325     * @param pm Supply a PackageManager used to load the input method's
326     * resources.
327     */
328    public Drawable loadIcon(PackageManager pm) {
329        return mService.loadIcon(pm);
330    }
331
332    /**
333     * Return the class name of an activity that provides a settings UI for
334     * the input method.  You can launch this activity be starting it with
335     * an {@link android.content.Intent} whose action is MAIN and with an
336     * explicit {@link android.content.ComponentName}
337     * composed of {@link #getPackageName} and the class name returned here.
338     *
339     * <p>A null will be returned if there is no settings activity associated
340     * with the input method.
341     */
342    public String getSettingsActivity() {
343        return mSettingsActivityName;
344    }
345
346    /**
347     * Return the count of the subtypes of Input Method.
348     */
349    public int getSubtypeCount() {
350        return mSubtypes.size();
351    }
352
353    /**
354     * Return the Input Method's subtype at the specified index.
355     *
356     * @param index the index of the subtype to return.
357     */
358    public InputMethodSubtype getSubtypeAt(int index) {
359        return mSubtypes.get(index);
360    }
361
362    /**
363     * Return the resource identifier of a resource inside of this input
364     * method's .apk that determines whether it should be considered a
365     * default input method for the system.
366     */
367    public int getIsDefaultResourceId() {
368        return mIsDefaultResId;
369    }
370
371    /**
372     * Return whether or not this ime is a default ime or not.
373     * @hide
374     */
375    public boolean isDefault(Context context) {
376        if (mForceDefault) {
377            return true;
378        }
379        try {
380            final Resources res = context.createPackageContext(getPackageName(), 0).getResources();
381            return res.getBoolean(getIsDefaultResourceId());
382        } catch (NameNotFoundException e) {
383            return false;
384        }
385    }
386
387    public void dump(Printer pw, String prefix) {
388        pw.println(prefix + "mId=" + mId
389                + " mSettingsActivityName=" + mSettingsActivityName);
390        pw.println(prefix + "mIsDefaultResId=0x"
391                + Integer.toHexString(mIsDefaultResId));
392        pw.println(prefix + "Service:");
393        mService.dump(pw, prefix + "  ");
394    }
395
396    @Override
397    public String toString() {
398        return "InputMethodInfo{" + mId
399                + ", settings: "
400                + mSettingsActivityName + "}";
401    }
402
403    /**
404     * Used to test whether the given parameter object is an
405     * {@link InputMethodInfo} and its Id is the same to this one.
406     *
407     * @return true if the given parameter object is an
408     *         {@link InputMethodInfo} and its Id is the same to this one.
409     */
410    @Override
411    public boolean equals(Object o) {
412        if (o == this) return true;
413        if (o == null) return false;
414
415        if (!(o instanceof InputMethodInfo)) return false;
416
417        InputMethodInfo obj = (InputMethodInfo) o;
418        return mId.equals(obj.mId);
419    }
420
421    @Override
422    public int hashCode() {
423        return mId.hashCode();
424    }
425
426    /**
427     * @hide
428     */
429    public boolean isAuxiliaryIme() {
430        return mIsAuxIme;
431    }
432
433    /**
434     * Used to package this object into a {@link Parcel}.
435     *
436     * @param dest The {@link Parcel} to be written.
437     * @param flags The flags used for parceling.
438     */
439    @Override
440    public void writeToParcel(Parcel dest, int flags) {
441        dest.writeString(mId);
442        dest.writeString(mSettingsActivityName);
443        dest.writeInt(mIsDefaultResId);
444        dest.writeInt(mIsAuxIme ? 1 : 0);
445        mService.writeToParcel(dest, flags);
446        dest.writeTypedList(mSubtypes);
447    }
448
449    /**
450     * Used to make this class parcelable.
451     */
452    public static final Parcelable.Creator<InputMethodInfo> CREATOR
453            = new Parcelable.Creator<InputMethodInfo>() {
454        @Override
455        public InputMethodInfo createFromParcel(Parcel source) {
456            return new InputMethodInfo(source);
457        }
458
459        @Override
460        public InputMethodInfo[] newArray(int size) {
461            return new InputMethodInfo[size];
462        }
463    };
464
465    @Override
466    public int describeContents() {
467        return 0;
468    }
469}
470