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