InputMethodInfo.java revision 5df0631003392c416c9617458dc2814f9eaec317
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 * 49 * <p>It should be defined in an XML resource file with an {@code <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 */ 60public 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