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