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.Resources.NotFoundException;
     31 import android.content.res.TypedArray;
     32 import android.content.res.XmlResourceParser;
     33 import android.graphics.drawable.Drawable;
     34 import android.os.Parcel;
     35 import android.os.Parcelable;
     36 import android.util.AttributeSet;
     37 import android.util.Printer;
     38 import android.util.Slog;
     39 import android.util.Xml;
     40 import android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder;
     41 import android.view.inputmethod.InputMethodSubtypeArray;
     42 
     43 import java.io.IOException;
     44 import java.util.ArrayList;
     45 import java.util.List;
     46 import 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  */
     62 public 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