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