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