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