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