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