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