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 17 package android.view.inputmethod; 18 19 import android.annotation.FlaggedApi; 20 import android.annotation.NonNull; 21 import android.annotation.Nullable; 22 import android.annotation.SuppressLint; 23 import android.annotation.SystemApi; 24 import android.annotation.TestApi; 25 import android.compat.annotation.UnsupportedAppUsage; 26 import android.content.ComponentName; 27 import android.content.Context; 28 import android.content.Intent; 29 import android.content.pm.ActivityInfo; 30 import android.content.pm.ApplicationInfo; 31 import android.content.pm.PackageManager; 32 import android.content.pm.PackageManager.NameNotFoundException; 33 import android.content.pm.ResolveInfo; 34 import android.content.pm.ServiceInfo; 35 import android.content.res.Configuration; 36 import android.content.res.Resources; 37 import android.content.res.Resources.NotFoundException; 38 import android.content.res.TypedArray; 39 import android.content.res.XmlResourceParser; 40 import android.graphics.drawable.Drawable; 41 import android.icu.util.ULocale; 42 import android.inputmethodservice.InputMethodService; 43 import android.os.Parcel; 44 import android.os.Parcelable; 45 import android.text.TextUtils; 46 import android.util.AttributeSet; 47 import android.util.Printer; 48 import android.util.Slog; 49 import android.util.Xml; 50 import android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder; 51 52 import org.xmlpull.v1.XmlPullParser; 53 import org.xmlpull.v1.XmlPullParserException; 54 55 import java.io.IOException; 56 import java.util.ArrayList; 57 import java.util.Collections; 58 import java.util.List; 59 60 /** 61 * This class is used to specify meta information of an input method. 62 * 63 * <p>It should be defined in an XML resource file with an {@code <input-method>} element. 64 * For more information, see the guide to 65 * <a href="{@docRoot}guide/topics/text/creating-input-method.html"> 66 * Creating an Input Method</a>.</p> 67 * 68 * @see InputMethodSubtype 69 * 70 * @attr ref android.R.styleable#InputMethod_settingsActivity 71 * @attr ref android.R.styleable#InputMethod_isDefault 72 * @attr ref android.R.styleable#InputMethod_supportsSwitchingToNextInputMethod 73 * @attr ref android.R.styleable#InputMethod_supportsInlineSuggestions 74 * @attr ref android.R.styleable#InputMethod_supportsInlineSuggestionsWithTouchExploration 75 * @attr ref android.R.styleable#InputMethod_suppressesSpellChecker 76 * @attr ref android.R.styleable#InputMethod_showInInputMethodPicker 77 * @attr ref android.R.styleable#InputMethod_configChanges 78 */ 79 public final class InputMethodInfo implements Parcelable { 80 81 /** 82 * {@link Intent#getAction() Intent action} for IME that 83 * {@link #supportsStylusHandwriting() supports stylus handwriting}. 84 * 85 * @see #createStylusHandwritingSettingsActivityIntent() 86 */ 87 public static final String ACTION_STYLUS_HANDWRITING_SETTINGS = 88 "android.view.inputmethod.action.STYLUS_HANDWRITING_SETTINGS"; 89 90 /** 91 * {@link Intent#getAction() Intent action} for the IME language settings. 92 * 93 * @see #createImeLanguageSettingsActivityIntent() 94 */ 95 @FlaggedApi(android.view.inputmethod.Flags.FLAG_IME_SWITCHER_REVAMP) 96 public static final String ACTION_IME_LANGUAGE_SETTINGS = 97 "android.view.inputmethod.action.IME_LANGUAGE_SETTINGS"; 98 99 /** 100 * Maximal length of a component name 101 * @hide 102 */ 103 @TestApi 104 public static final int COMPONENT_NAME_MAX_LENGTH = 1000; 105 106 /** 107 * The maximum amount of IMEs that are loaded per package (in order). 108 * If a package contains more IMEs, they will be ignored and cannot be enabled. 109 * @hide 110 */ 111 @TestApi 112 @SuppressLint("MinMaxConstant") 113 public static final int MAX_IMES_PER_PACKAGE = 20; 114 115 static final String TAG = "InputMethodInfo"; 116 117 /** 118 * The Service that implements this input method component. 119 */ 120 final ResolveInfo mService; 121 122 /** 123 * IME only supports VR mode. 124 */ 125 final boolean mIsVrOnly; 126 127 /** 128 * IME only supports virtual devices. 129 */ 130 final boolean mIsVirtualDeviceOnly; 131 132 /** 133 * The unique string Id to identify the input method. This is generated 134 * from the input method component. 135 */ 136 final String mId; 137 138 /** 139 * The input method setting activity's name, used by the system settings to 140 * launch the setting activity of this input method. 141 */ 142 final String mSettingsActivityName; 143 144 /** 145 * The input method language settings activity's name, used to 146 * launch the language settings activity of this input method. 147 */ 148 @Nullable 149 private final String mLanguageSettingsActivityName; 150 151 /** 152 * The resource in the input method's .apk that holds a boolean indicating 153 * whether it should be considered the default input method for this 154 * system. This is a resource ID instead of the final value so that it 155 * can change based on the configuration (in particular locale). 156 */ 157 final int mIsDefaultResId; 158 159 /** 160 * An array-like container of the subtypes. 161 */ 162 @UnsupportedAppUsage 163 private final InputMethodSubtypeArray mSubtypes; 164 165 private final boolean mIsAuxIme; 166 167 /** 168 * Caveat: mForceDefault must be false for production. This flag is only for test. 169 */ 170 private final boolean mForceDefault; 171 172 /** 173 * The flag whether this IME supports ways to switch to a next input method (e.g. globe key.) 174 */ 175 private final boolean mSupportsSwitchingToNextInputMethod; 176 177 /** 178 * The flag whether this IME supports inline suggestions. 179 */ 180 private final boolean mInlineSuggestionsEnabled; 181 182 /** 183 * The flag whether this IME supports inline suggestions when touch exploration is enabled. 184 */ 185 private final boolean mSupportsInlineSuggestionsWithTouchExploration; 186 187 /** 188 * The flag whether this IME suppresses spell checker. 189 */ 190 private final boolean mSuppressesSpellChecker; 191 192 /** 193 * The flag whether this IME should be shown as an option in the IME picker. 194 */ 195 private final boolean mShowInInputMethodPicker; 196 197 /** 198 * The flag for configurations IME assumes the responsibility for handling in 199 * {@link InputMethodService#onConfigurationChanged(Configuration)}}. 200 */ 201 private final int mHandledConfigChanges; 202 203 /** 204 * The flag whether this IME supports Handwriting using stylus input. 205 */ 206 private final boolean mSupportsStylusHandwriting; 207 208 /** The flag whether this IME supports connectionless stylus handwriting sessions. */ 209 private final boolean mSupportsConnectionlessStylusHandwriting; 210 211 /** 212 * The stylus handwriting setting activity's name, used by the system settings to 213 * launch the stylus handwriting specific setting activity of this input method. 214 */ 215 private final String mStylusHandwritingSettingsActivityAttr; 216 217 /** 218 * @param service the {@link ResolveInfo} corresponds in which the IME is implemented. 219 * @return a unique ID to be returned by {@link #getId()}. We have used 220 * {@link ComponentName#flattenToShortString()} for this purpose (and it is already 221 * unrealistic to switch to a different scheme as it is already implicitly assumed in 222 * many places). 223 * @hide 224 */ computeId(@onNull ResolveInfo service)225 public static String computeId(@NonNull ResolveInfo service) { 226 final ServiceInfo si = service.serviceInfo; 227 return new ComponentName(si.packageName, si.name).flattenToShortString(); 228 } 229 230 /** 231 * Constructor. 232 * 233 * @param context The Context in which we are parsing the input method. 234 * @param service The ResolveInfo returned from the package manager about 235 * this input method's component. 236 */ InputMethodInfo(Context context, ResolveInfo service)237 public InputMethodInfo(Context context, ResolveInfo service) 238 throws XmlPullParserException, IOException { 239 this(context, service, null); 240 } 241 242 /** 243 * Constructor. 244 * 245 * @param context The Context in which we are parsing the input method. 246 * @param service The ResolveInfo returned from the package manager about 247 * this input method's component. 248 * @param additionalSubtypes additional subtypes being added to this InputMethodInfo 249 * @hide 250 */ InputMethodInfo(Context context, ResolveInfo service, List<InputMethodSubtype> additionalSubtypes)251 public InputMethodInfo(Context context, ResolveInfo service, 252 List<InputMethodSubtype> additionalSubtypes) 253 throws XmlPullParserException, IOException { 254 mService = service; 255 ServiceInfo si = service.serviceInfo; 256 mId = computeId(service); 257 boolean isAuxIme = true; 258 boolean supportsSwitchingToNextInputMethod = false; // false as default 259 boolean inlineSuggestionsEnabled = false; // false as default 260 boolean supportsInlineSuggestionsWithTouchExploration = false; // false as default 261 boolean suppressesSpellChecker = false; // false as default 262 boolean showInInputMethodPicker = true; // true as default 263 mForceDefault = false; 264 265 PackageManager pm = context.getPackageManager(); 266 String settingsActivityComponent = null; 267 String languageSettingsActivityComponent = null; 268 String stylusHandwritingSettingsActivity = null; 269 boolean isVrOnly; 270 boolean isVirtualDeviceOnly; 271 int isDefaultResId = 0; 272 273 XmlResourceParser parser = null; 274 final ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>(); 275 try { 276 parser = si.loadXmlMetaData(pm, InputMethod.SERVICE_META_DATA); 277 if (parser == null) { 278 throw new XmlPullParserException("No " 279 + InputMethod.SERVICE_META_DATA + " meta-data"); 280 } 281 282 Resources res = pm.getResourcesForApplication(si.applicationInfo); 283 284 AttributeSet attrs = Xml.asAttributeSet(parser); 285 286 int type; 287 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT 288 && type != XmlPullParser.START_TAG) { 289 } 290 291 String nodeName = parser.getName(); 292 if (!"input-method".equals(nodeName)) { 293 throw new XmlPullParserException( 294 "Meta-data does not start with input-method tag"); 295 } 296 297 TypedArray sa = res.obtainAttributes(attrs, 298 com.android.internal.R.styleable.InputMethod); 299 settingsActivityComponent = sa.getString( 300 com.android.internal.R.styleable.InputMethod_settingsActivity); 301 if (Flags.imeSwitcherRevamp()) { 302 languageSettingsActivityComponent = sa.getString( 303 com.android.internal.R.styleable.InputMethod_languageSettingsActivity); 304 } 305 if ((si.name != null && si.name.length() > COMPONENT_NAME_MAX_LENGTH) 306 || (settingsActivityComponent != null 307 && settingsActivityComponent.length() 308 > COMPONENT_NAME_MAX_LENGTH) 309 || (languageSettingsActivityComponent != null 310 && languageSettingsActivityComponent.length() 311 > COMPONENT_NAME_MAX_LENGTH)) { 312 throw new XmlPullParserException( 313 "Activity name exceeds maximum of 1000 characters"); 314 } 315 316 isVrOnly = sa.getBoolean(com.android.internal.R.styleable.InputMethod_isVrOnly, false); 317 isVirtualDeviceOnly = sa.getBoolean( 318 com.android.internal.R.styleable.InputMethod_isVirtualDeviceOnly, false); 319 isDefaultResId = sa.getResourceId( 320 com.android.internal.R.styleable.InputMethod_isDefault, 0); 321 supportsSwitchingToNextInputMethod = sa.getBoolean( 322 com.android.internal.R.styleable.InputMethod_supportsSwitchingToNextInputMethod, 323 false); 324 inlineSuggestionsEnabled = sa.getBoolean( 325 com.android.internal.R.styleable.InputMethod_supportsInlineSuggestions, false); 326 supportsInlineSuggestionsWithTouchExploration = sa.getBoolean( 327 com.android.internal.R.styleable 328 .InputMethod_supportsInlineSuggestionsWithTouchExploration, false); 329 suppressesSpellChecker = sa.getBoolean( 330 com.android.internal.R.styleable.InputMethod_suppressesSpellChecker, false); 331 showInInputMethodPicker = sa.getBoolean( 332 com.android.internal.R.styleable.InputMethod_showInInputMethodPicker, true); 333 mHandledConfigChanges = sa.getInt( 334 com.android.internal.R.styleable.InputMethod_configChanges, 0); 335 mSupportsStylusHandwriting = sa.getBoolean( 336 com.android.internal.R.styleable.InputMethod_supportsStylusHandwriting, false); 337 mSupportsConnectionlessStylusHandwriting = sa.getBoolean( 338 com.android.internal.R.styleable 339 .InputMethod_supportsConnectionlessStylusHandwriting, false); 340 stylusHandwritingSettingsActivity = sa.getString( 341 com.android.internal.R.styleable.InputMethod_stylusHandwritingSettingsActivity); 342 sa.recycle(); 343 344 final int depth = parser.getDepth(); 345 // Parse all subtypes 346 while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth) 347 && type != XmlPullParser.END_DOCUMENT) { 348 if (type == XmlPullParser.START_TAG) { 349 nodeName = parser.getName(); 350 if (!"subtype".equals(nodeName)) { 351 throw new XmlPullParserException( 352 "Meta-data in input-method does not start with subtype tag"); 353 } 354 final TypedArray a = res.obtainAttributes( 355 attrs, com.android.internal.R.styleable.InputMethod_Subtype); 356 String pkLanguageTag = a.getString(com.android.internal.R.styleable 357 .InputMethod_Subtype_physicalKeyboardHintLanguageTag); 358 String pkLayoutType = a.getString(com.android.internal.R.styleable 359 .InputMethod_Subtype_physicalKeyboardHintLayoutType); 360 final InputMethodSubtype subtype = new InputMethodSubtypeBuilder() 361 .setSubtypeNameResId(a.getResourceId(com.android.internal.R.styleable 362 .InputMethod_Subtype_label, 0)) 363 .setSubtypeIconResId(a.getResourceId(com.android.internal.R.styleable 364 .InputMethod_Subtype_icon, 0)) 365 .setPhysicalKeyboardHint( 366 pkLanguageTag == null ? null : new ULocale(pkLanguageTag), 367 pkLayoutType == null ? "" : pkLayoutType) 368 .setLanguageTag(a.getString(com.android.internal.R.styleable 369 .InputMethod_Subtype_languageTag)) 370 .setSubtypeLocale(a.getString(com.android.internal.R.styleable 371 .InputMethod_Subtype_imeSubtypeLocale)) 372 .setSubtypeMode(a.getString(com.android.internal.R.styleable 373 .InputMethod_Subtype_imeSubtypeMode)) 374 .setSubtypeExtraValue(a.getString(com.android.internal.R.styleable 375 .InputMethod_Subtype_imeSubtypeExtraValue)) 376 .setIsAuxiliary(a.getBoolean(com.android.internal.R.styleable 377 .InputMethod_Subtype_isAuxiliary, false)) 378 .setOverridesImplicitlyEnabledSubtype(a.getBoolean( 379 com.android.internal.R.styleable 380 .InputMethod_Subtype_overridesImplicitlyEnabledSubtype, false)) 381 .setSubtypeId(a.getInt(com.android.internal.R.styleable 382 .InputMethod_Subtype_subtypeId, 0 /* use Arrays.hashCode */)) 383 .setIsAsciiCapable(a.getBoolean(com.android.internal.R.styleable 384 .InputMethod_Subtype_isAsciiCapable, false)).build(); 385 a.recycle(); 386 if (!subtype.isAuxiliary()) { 387 isAuxIme = false; 388 } 389 subtypes.add(subtype); 390 } 391 } 392 } catch (NameNotFoundException | IndexOutOfBoundsException | NumberFormatException e) { 393 throw new XmlPullParserException( 394 "Unable to create context for: " + si.packageName); 395 } finally { 396 if (parser != null) parser.close(); 397 } 398 399 if (subtypes.size() == 0) { 400 isAuxIme = false; 401 } 402 403 if (additionalSubtypes != null) { 404 final int N = additionalSubtypes.size(); 405 for (int i = 0; i < N; ++i) { 406 final InputMethodSubtype subtype = additionalSubtypes.get(i); 407 if (!subtypes.contains(subtype)) { 408 subtypes.add(subtype); 409 } else { 410 Slog.w(TAG, "Duplicated subtype definition found: " 411 + subtype.getLocale() + ", " + subtype.getMode()); 412 } 413 } 414 } 415 mSubtypes = new InputMethodSubtypeArray(subtypes); 416 mSettingsActivityName = settingsActivityComponent; 417 mLanguageSettingsActivityName = languageSettingsActivityComponent; 418 mStylusHandwritingSettingsActivityAttr = stylusHandwritingSettingsActivity; 419 mIsDefaultResId = isDefaultResId; 420 mIsAuxIme = isAuxIme; 421 mSupportsSwitchingToNextInputMethod = supportsSwitchingToNextInputMethod; 422 mInlineSuggestionsEnabled = inlineSuggestionsEnabled; 423 mSupportsInlineSuggestionsWithTouchExploration = 424 supportsInlineSuggestionsWithTouchExploration; 425 mSuppressesSpellChecker = suppressesSpellChecker; 426 mShowInInputMethodPicker = showInInputMethodPicker; 427 mIsVrOnly = isVrOnly; 428 mIsVirtualDeviceOnly = isVirtualDeviceOnly; 429 } 430 431 /** 432 * @hide 433 */ InputMethodInfo(InputMethodInfo source)434 public InputMethodInfo(InputMethodInfo source) { 435 this(source, Collections.emptyList()); 436 } 437 438 /** 439 * @hide 440 */ InputMethodInfo(@onNull InputMethodInfo source, @NonNull List<InputMethodSubtype> additionalSubtypes)441 public InputMethodInfo(@NonNull InputMethodInfo source, 442 @NonNull List<InputMethodSubtype> additionalSubtypes) { 443 mId = source.mId; 444 mSettingsActivityName = source.mSettingsActivityName; 445 mLanguageSettingsActivityName = source.mLanguageSettingsActivityName; 446 mIsDefaultResId = source.mIsDefaultResId; 447 mIsAuxIme = source.mIsAuxIme; 448 mSupportsSwitchingToNextInputMethod = source.mSupportsSwitchingToNextInputMethod; 449 mInlineSuggestionsEnabled = source.mInlineSuggestionsEnabled; 450 mSupportsInlineSuggestionsWithTouchExploration = 451 source.mSupportsInlineSuggestionsWithTouchExploration; 452 mSuppressesSpellChecker = source.mSuppressesSpellChecker; 453 mShowInInputMethodPicker = source.mShowInInputMethodPicker; 454 mIsVrOnly = source.mIsVrOnly; 455 mIsVirtualDeviceOnly = source.mIsVirtualDeviceOnly; 456 mService = source.mService; 457 if (additionalSubtypes.isEmpty()) { 458 mSubtypes = source.mSubtypes; 459 } else { 460 final ArrayList<InputMethodSubtype> subtypes = source.mSubtypes.toList(); 461 final int additionalSubtypeCount = additionalSubtypes.size(); 462 for (int i = 0; i < additionalSubtypeCount; ++i) { 463 final InputMethodSubtype additionalSubtype = additionalSubtypes.get(i); 464 if (!subtypes.contains(additionalSubtype)) { 465 subtypes.add(additionalSubtype); 466 } 467 } 468 mSubtypes = new InputMethodSubtypeArray(subtypes); 469 } 470 mHandledConfigChanges = source.mHandledConfigChanges; 471 mSupportsStylusHandwriting = source.mSupportsStylusHandwriting; 472 mSupportsConnectionlessStylusHandwriting = source.mSupportsConnectionlessStylusHandwriting; 473 mForceDefault = source.mForceDefault; 474 mStylusHandwritingSettingsActivityAttr = source.mStylusHandwritingSettingsActivityAttr; 475 } 476 InputMethodInfo(Parcel source)477 InputMethodInfo(Parcel source) { 478 mId = source.readString(); 479 mSettingsActivityName = source.readString(); 480 mLanguageSettingsActivityName = source.readString8(); 481 mIsDefaultResId = source.readInt(); 482 mIsAuxIme = source.readInt() == 1; 483 mSupportsSwitchingToNextInputMethod = source.readInt() == 1; 484 mInlineSuggestionsEnabled = source.readInt() == 1; 485 mSupportsInlineSuggestionsWithTouchExploration = source.readInt() == 1; 486 mSuppressesSpellChecker = source.readBoolean(); 487 mShowInInputMethodPicker = source.readBoolean(); 488 mIsVrOnly = source.readBoolean(); 489 mIsVirtualDeviceOnly = source.readBoolean(); 490 mService = ResolveInfo.CREATOR.createFromParcel(source); 491 mSubtypes = new InputMethodSubtypeArray(source); 492 mHandledConfigChanges = source.readInt(); 493 mSupportsStylusHandwriting = source.readBoolean(); 494 mSupportsConnectionlessStylusHandwriting = source.readBoolean(); 495 mStylusHandwritingSettingsActivityAttr = source.readString8(); 496 mForceDefault = false; 497 } 498 499 /** 500 * Temporary API for creating a built-in input method for test. 501 */ InputMethodInfo(String packageName, String className, CharSequence label, String settingsActivity)502 public InputMethodInfo(String packageName, String className, 503 CharSequence label, String settingsActivity) { 504 this(buildFakeResolveInfo(packageName, className, label), false /* isAuxIme */, 505 settingsActivity, null /* languageSettingsActivity */, null /* subtypes */, 506 0 /* isDefaultResId */, false /* forceDefault */, 507 true /* supportsSwitchingToNextInputMethod */, 508 false /* inlineSuggestionsEnabled */, false /* isVrOnly */, 509 false /* isVirtualDeviceOnly */, 0 /* handledConfigChanges */, 510 false /* supportsStylusHandwriting */, 511 false /* supportConnectionlessStylusHandwriting */, 512 null /* stylusHandwritingSettingsActivityAttr */, 513 false /* inlineSuggestionsEnabled */); 514 } 515 516 /** 517 * Test API for creating a built-in input method to verify stylus handwriting. 518 * @hide 519 */ 520 @TestApi InputMethodInfo(@onNull String packageName, @NonNull String className, @NonNull CharSequence label, @NonNull String settingsActivity, boolean supportStylusHandwriting, @NonNull String stylusHandwritingSettingsActivityAttr)521 public InputMethodInfo(@NonNull String packageName, @NonNull String className, 522 @NonNull CharSequence label, @NonNull String settingsActivity, 523 boolean supportStylusHandwriting, 524 @NonNull String stylusHandwritingSettingsActivityAttr) { 525 this(buildFakeResolveInfo(packageName, className, label), false /* isAuxIme */, 526 settingsActivity, null /* languageSettingsActivity */, 527 null /* subtypes */, 0 /* isDefaultResId */, 528 false /* forceDefault */, true /* supportsSwitchingToNextInputMethod */, 529 false /* inlineSuggestionsEnabled */, false /* isVrOnly */, 530 false /* isVirtualDeviceOnly */, 0 /* handledConfigChanges */, 531 supportStylusHandwriting, false /* supportConnectionlessStylusHandwriting */, 532 stylusHandwritingSettingsActivityAttr, false /* inlineSuggestionsEnabled */); 533 } 534 535 /** 536 * Test API for creating a built-in input method to verify stylus handwriting. 537 * @hide 538 */ 539 @TestApi InputMethodInfo(@onNull String packageName, @NonNull String className, @NonNull CharSequence label, @NonNull String settingsActivity, @NonNull String languageSettingsActivity, boolean supportStylusHandwriting, @NonNull String stylusHandwritingSettingsActivityAttr)540 public InputMethodInfo(@NonNull String packageName, @NonNull String className, 541 @NonNull CharSequence label, @NonNull String settingsActivity, 542 @NonNull String languageSettingsActivity, boolean supportStylusHandwriting, 543 @NonNull String stylusHandwritingSettingsActivityAttr) { 544 this(buildFakeResolveInfo(packageName, className, label), false /* isAuxIme */, 545 settingsActivity, languageSettingsActivity, null /* subtypes */, 546 0 /* isDefaultResId */, false /* forceDefault */, 547 true /* supportsSwitchingToNextInputMethod */, 548 false /* inlineSuggestionsEnabled */, false /* isVrOnly */, 549 false /* isVirtualDeviceOnly */, 0 /* handledConfigChanges */, 550 supportStylusHandwriting, false /* supportConnectionlessStylusHandwriting */, 551 stylusHandwritingSettingsActivityAttr, false /* inlineSuggestionsEnabled */); 552 } 553 554 /** 555 * Test API for creating a built-in input method to verify stylus handwriting. 556 * @hide 557 */ 558 @TestApi 559 @FlaggedApi(Flags.FLAG_CONNECTIONLESS_HANDWRITING) InputMethodInfo(@onNull String packageName, @NonNull String className, @NonNull CharSequence label, @NonNull String settingsActivity, @NonNull String languageSettingsActivity, boolean supportStylusHandwriting, boolean supportConnectionlessStylusHandwriting, @NonNull String stylusHandwritingSettingsActivityAttr)560 public InputMethodInfo(@NonNull String packageName, @NonNull String className, 561 @NonNull CharSequence label, @NonNull String settingsActivity, 562 @NonNull String languageSettingsActivity, boolean supportStylusHandwriting, 563 boolean supportConnectionlessStylusHandwriting, 564 @NonNull String stylusHandwritingSettingsActivityAttr) { 565 this(buildFakeResolveInfo(packageName, className, label), false /* isAuxIme */, 566 settingsActivity, languageSettingsActivity, null /* subtypes */, 567 0 /* isDefaultResId */, false /* forceDefault */, 568 true /* supportsSwitchingToNextInputMethod */, 569 false /* inlineSuggestionsEnabled */, false /* isVrOnly */, 570 false /* isVirtualDeviceOnly */, 0 /* handledConfigChanges */, 571 supportStylusHandwriting, supportConnectionlessStylusHandwriting, 572 stylusHandwritingSettingsActivityAttr, false /* inlineSuggestionsEnabled */); 573 } 574 575 /** 576 * Temporary API for creating a built-in input method for test. 577 * @hide 578 */ 579 @TestApi InputMethodInfo(@onNull String packageName, @NonNull String className, @NonNull CharSequence label, @NonNull String settingsActivity, int handledConfigChanges)580 public InputMethodInfo(@NonNull String packageName, @NonNull String className, 581 @NonNull CharSequence label, @NonNull String settingsActivity, 582 int handledConfigChanges) { 583 this(buildFakeResolveInfo(packageName, className, label), false /* isAuxIme */, 584 settingsActivity, null /* languageSettingsActivity */, null /* subtypes */, 585 0 /* isDefaultResId */, false /* forceDefault */, 586 true /* supportsSwitchingToNextInputMethod */, 587 false /* inlineSuggestionsEnabled */, false /* isVrOnly */, 588 false /* isVirtualDeviceOnly */, handledConfigChanges, 589 false /* supportsStylusHandwriting */, 590 false /* supportConnectionlessStylusHandwriting */, 591 null /* stylusHandwritingSettingsActivityAttr */, 592 false /* inlineSuggestionsEnabled */); 593 } 594 595 /** 596 * Temporary API for creating a built-in input method for test. 597 * @hide 598 */ InputMethodInfo(ResolveInfo ri, boolean isAuxIme, String settingsActivity, List<InputMethodSubtype> subtypes, int isDefaultResId, boolean forceDefault)599 public InputMethodInfo(ResolveInfo ri, boolean isAuxIme, 600 String settingsActivity, List<InputMethodSubtype> subtypes, int isDefaultResId, 601 boolean forceDefault) { 602 this(ri, isAuxIme, settingsActivity, null /* languageSettingsActivity */, subtypes, 603 isDefaultResId, forceDefault, 604 true /* supportsSwitchingToNextInputMethod */, false /* inlineSuggestionsEnabled */, 605 false /* isVrOnly */, false /* isVirtualDeviceOnly */, 0 /* handledconfigChanges */, 606 false /* supportsStylusHandwriting */, 607 false /* supportConnectionlessStylusHandwriting */, 608 null /* stylusHandwritingSettingsActivityAttr */, 609 false /* inlineSuggestionsEnabled */); 610 } 611 612 /** 613 * Temporary API for creating a built-in input method for test. 614 * @hide 615 */ InputMethodInfo(ResolveInfo ri, boolean isAuxIme, String settingsActivity, List<InputMethodSubtype> subtypes, int isDefaultResId, boolean forceDefault, boolean supportsSwitchingToNextInputMethod, boolean isVrOnly)616 public InputMethodInfo(ResolveInfo ri, boolean isAuxIme, String settingsActivity, 617 List<InputMethodSubtype> subtypes, int isDefaultResId, boolean forceDefault, 618 boolean supportsSwitchingToNextInputMethod, boolean isVrOnly) { 619 this(ri, isAuxIme, settingsActivity, null /* languageSettingsActivity */, subtypes, 620 isDefaultResId, forceDefault, 621 supportsSwitchingToNextInputMethod, false /* inlineSuggestionsEnabled */, isVrOnly, 622 false /* isVirtualDeviceOnly */, 623 0 /* handledConfigChanges */, false /* supportsStylusHandwriting */, 624 false /* supportConnectionlessStylusHandwriting */, 625 null /* stylusHandwritingSettingsActivityAttr */, 626 false /* inlineSuggestionsEnabled */); 627 } 628 629 /** 630 * Temporary API for creating a built-in input method for test. 631 * @hide 632 */ InputMethodInfo(ResolveInfo ri, boolean isAuxIme, String settingsActivity, @Nullable String languageSettingsActivity, List<InputMethodSubtype> subtypes, int isDefaultResId, boolean forceDefault, boolean supportsSwitchingToNextInputMethod, boolean inlineSuggestionsEnabled, boolean isVrOnly, boolean isVirtualDeviceOnly, int handledConfigChanges, boolean supportsStylusHandwriting, boolean supportsConnectionlessStylusHandwriting, String stylusHandwritingSettingsActivityAttr, boolean supportsInlineSuggestionsWithTouchExploration)633 public InputMethodInfo(ResolveInfo ri, boolean isAuxIme, String settingsActivity, 634 @Nullable String languageSettingsActivity, List<InputMethodSubtype> subtypes, 635 int isDefaultResId, boolean forceDefault, 636 boolean supportsSwitchingToNextInputMethod, boolean inlineSuggestionsEnabled, 637 boolean isVrOnly, boolean isVirtualDeviceOnly, int handledConfigChanges, 638 boolean supportsStylusHandwriting, boolean supportsConnectionlessStylusHandwriting, 639 String stylusHandwritingSettingsActivityAttr, 640 boolean supportsInlineSuggestionsWithTouchExploration) { 641 final ServiceInfo si = ri.serviceInfo; 642 mService = ri; 643 mId = new ComponentName(si.packageName, si.name).flattenToShortString(); 644 mSettingsActivityName = settingsActivity; 645 mLanguageSettingsActivityName = languageSettingsActivity; 646 mIsDefaultResId = isDefaultResId; 647 mIsAuxIme = isAuxIme; 648 mSubtypes = new InputMethodSubtypeArray(subtypes); 649 mForceDefault = forceDefault; 650 mSupportsSwitchingToNextInputMethod = supportsSwitchingToNextInputMethod; 651 mInlineSuggestionsEnabled = inlineSuggestionsEnabled; 652 mSupportsInlineSuggestionsWithTouchExploration = 653 supportsInlineSuggestionsWithTouchExploration; 654 mSuppressesSpellChecker = false; 655 mShowInInputMethodPicker = true; 656 mIsVrOnly = isVrOnly; 657 mIsVirtualDeviceOnly = isVirtualDeviceOnly; 658 mHandledConfigChanges = handledConfigChanges; 659 mSupportsStylusHandwriting = supportsStylusHandwriting; 660 mSupportsConnectionlessStylusHandwriting = supportsConnectionlessStylusHandwriting; 661 mStylusHandwritingSettingsActivityAttr = stylusHandwritingSettingsActivityAttr; 662 } 663 buildFakeResolveInfo(String packageName, String className, CharSequence label)664 private static ResolveInfo buildFakeResolveInfo(String packageName, String className, 665 CharSequence label) { 666 ResolveInfo ri = new ResolveInfo(); 667 ServiceInfo si = new ServiceInfo(); 668 ApplicationInfo ai = new ApplicationInfo(); 669 ai.packageName = packageName; 670 ai.enabled = true; 671 si.applicationInfo = ai; 672 si.enabled = true; 673 si.packageName = packageName; 674 si.name = className; 675 si.exported = true; 676 si.nonLocalizedLabel = label; 677 ri.serviceInfo = si; 678 return ri; 679 } 680 681 /** 682 * @return a unique ID for this input method, which is guaranteed to be the same as the result 683 * of {@code getComponent().flattenToShortString()}. 684 * @see ComponentName#unflattenFromString(String) 685 */ getId()686 public String getId() { 687 return mId; 688 } 689 690 /** 691 * Return the .apk package that implements this input method. 692 */ getPackageName()693 public String getPackageName() { 694 return mService.serviceInfo.packageName; 695 } 696 697 /** 698 * Return the class name of the service component that implements 699 * this input method. 700 */ getServiceName()701 public String getServiceName() { 702 return mService.serviceInfo.name; 703 } 704 705 /** 706 * Return the raw information about the Service implementing this 707 * input method. Do not modify the returned object. 708 */ getServiceInfo()709 public ServiceInfo getServiceInfo() { 710 return mService.serviceInfo; 711 } 712 713 /** 714 * Return the component of the service that implements this input 715 * method. 716 */ getComponent()717 public ComponentName getComponent() { 718 return new ComponentName(mService.serviceInfo.packageName, 719 mService.serviceInfo.name); 720 } 721 722 /** 723 * Load the user-displayed label for this input method. 724 * 725 * @param pm Supply a PackageManager used to load the input method's 726 * resources. 727 */ loadLabel(PackageManager pm)728 public CharSequence loadLabel(PackageManager pm) { 729 return mService.loadLabel(pm); 730 } 731 732 /** 733 * Load the user-displayed icon for this input method. 734 * 735 * @param pm Supply a PackageManager used to load the input method's 736 * resources. 737 */ loadIcon(PackageManager pm)738 public Drawable loadIcon(PackageManager pm) { 739 return mService.loadIcon(pm); 740 } 741 742 /** 743 * Return the class name of an activity that provides a settings UI for 744 * the input method. You can launch this activity be starting it with 745 * an {@link android.content.Intent} whose action is MAIN and with an 746 * explicit {@link android.content.ComponentName} 747 * composed of {@link #getPackageName} and the class name returned here. 748 * 749 * <p>A null will be returned if there is no settings activity associated 750 * with the input method.</p> 751 * @see #createStylusHandwritingSettingsActivityIntent() 752 */ getSettingsActivity()753 public String getSettingsActivity() { 754 return mSettingsActivityName; 755 } 756 757 /** 758 * Returns true if IME supports VR mode only. 759 * @hide 760 */ isVrOnly()761 public boolean isVrOnly() { 762 return mIsVrOnly; 763 } 764 765 /** 766 * Returns true if IME supports only virtual devices. 767 * @hide 768 */ 769 @FlaggedApi(android.companion.virtual.flags.Flags.FLAG_VDM_CUSTOM_IME) 770 @SystemApi isVirtualDeviceOnly()771 public boolean isVirtualDeviceOnly() { 772 return mIsVirtualDeviceOnly; 773 } 774 775 /** 776 * Return the count of the subtypes of Input Method. 777 */ getSubtypeCount()778 public int getSubtypeCount() { 779 return mSubtypes.getCount(); 780 } 781 782 /** 783 * Return the Input Method's subtype at the specified index. 784 * 785 * @param index the index of the subtype to return. 786 */ getSubtypeAt(int index)787 public InputMethodSubtype getSubtypeAt(int index) { 788 return mSubtypes.get(index); 789 } 790 791 /** 792 * Return the resource identifier of a resource inside of this input 793 * method's .apk that determines whether it should be considered a 794 * default input method for the system. 795 */ getIsDefaultResourceId()796 public int getIsDefaultResourceId() { 797 return mIsDefaultResId; 798 } 799 800 /** 801 * Return whether or not this ime is a default ime or not. 802 * @hide 803 */ 804 @UnsupportedAppUsage isDefault(Context context)805 public boolean isDefault(Context context) { 806 if (mForceDefault) { 807 return true; 808 } 809 try { 810 if (getIsDefaultResourceId() == 0) { 811 return false; 812 } 813 final Resources res = context.createPackageContext(getPackageName(), 0).getResources(); 814 return res.getBoolean(getIsDefaultResourceId()); 815 } catch (NameNotFoundException | NotFoundException e) { 816 return false; 817 } 818 } 819 820 /** 821 * Returns the bit mask of kinds of configuration changes that this IME 822 * can handle itself (without being restarted by the system). 823 * 824 * @attr ref android.R.styleable#InputMethod_configChanges 825 */ 826 @ActivityInfo.Config getConfigChanges()827 public int getConfigChanges() { 828 return mHandledConfigChanges; 829 } 830 831 /** 832 * Returns if IME supports handwriting using stylus input. 833 * @attr ref android.R.styleable#InputMethod_supportsStylusHandwriting 834 * @see #createStylusHandwritingSettingsActivityIntent() 835 */ supportsStylusHandwriting()836 public boolean supportsStylusHandwriting() { 837 return mSupportsStylusHandwriting; 838 } 839 840 /** 841 * Returns whether the IME supports connectionless stylus handwriting sessions. 842 * 843 * @attr ref android.R.styleable#InputMethod_supportsConnectionlessStylusHandwriting 844 */ 845 @FlaggedApi(Flags.FLAG_CONNECTIONLESS_HANDWRITING) supportsConnectionlessStylusHandwriting()846 public boolean supportsConnectionlessStylusHandwriting() { 847 return mSupportsConnectionlessStylusHandwriting; 848 } 849 850 /** 851 * Returns {@link Intent} for stylus handwriting settings activity with 852 * {@link Intent#getAction() Intent action} {@link #ACTION_STYLUS_HANDWRITING_SETTINGS} 853 * if IME {@link #supportsStylusHandwriting() supports stylus handwriting}, else 854 * <code>null</code> if there are no associated settings for stylus handwriting / handwriting 855 * is not supported or if 856 * {@link android.R.styleable#InputMethod_stylusHandwritingSettingsActivity} is not defined. 857 * 858 * <p>To launch stylus settings, use this method to get the {@link android.content.Intent} to 859 * launch the stylus handwriting settings activity.</p> 860 * <p>e.g.<pre><code>startActivity(createStylusHandwritingSettingsActivityIntent());</code> 861 * </pre></p> 862 * 863 * @attr ref R.styleable#InputMethod_stylusHandwritingSettingsActivity 864 * @see #getSettingsActivity() 865 * @see #supportsStylusHandwriting() 866 */ 867 @Nullable createStylusHandwritingSettingsActivityIntent()868 public Intent createStylusHandwritingSettingsActivityIntent() { 869 if (TextUtils.isEmpty(mStylusHandwritingSettingsActivityAttr) 870 || !mSupportsStylusHandwriting) { 871 return null; 872 } 873 // TODO(b/210039666): consider returning null if component is not enabled. 874 return new Intent(ACTION_STYLUS_HANDWRITING_SETTINGS).setComponent( 875 new ComponentName(getServiceInfo().packageName, 876 mStylusHandwritingSettingsActivityAttr)); 877 } 878 879 /** 880 * Returns {@link Intent} for IME language settings activity with 881 * {@link Intent#getAction() Intent action} {@link #ACTION_IME_LANGUAGE_SETTINGS}, 882 * else <code>null</code> if 883 * {@link android.R.styleable#InputMethod_languageSettingsActivity} is not defined. 884 * 885 * <p>To launch IME language settings, use this method to get the {@link Intent} to launch 886 * the IME language settings activity.</p> 887 * <p>e.g.<pre><code>startActivity(createImeLanguageSettingsActivityIntent());</code></pre></p> 888 * 889 * @attr ref R.styleable#InputMethod_languageSettingsActivity 890 */ 891 @FlaggedApi(android.view.inputmethod.Flags.FLAG_IME_SWITCHER_REVAMP) 892 @Nullable createImeLanguageSettingsActivityIntent()893 public Intent createImeLanguageSettingsActivityIntent() { 894 if (TextUtils.isEmpty(mLanguageSettingsActivityName)) { 895 return null; 896 } 897 return new Intent(ACTION_IME_LANGUAGE_SETTINGS).setComponent( 898 new ComponentName(getServiceInfo().packageName, 899 mLanguageSettingsActivityName) 900 ); 901 } 902 dump(Printer pw, String prefix)903 public void dump(Printer pw, String prefix) { 904 pw.println(prefix + "mId=" + mId 905 + " mSettingsActivityName=" + mSettingsActivityName 906 + " mLanguageSettingsActivityName=" + mLanguageSettingsActivityName 907 + " mIsVrOnly=" + mIsVrOnly 908 + " mIsVirtualDeviceOnly=" + mIsVirtualDeviceOnly 909 + " mSupportsSwitchingToNextInputMethod=" + mSupportsSwitchingToNextInputMethod 910 + " mInlineSuggestionsEnabled=" + mInlineSuggestionsEnabled 911 + " mSupportsInlineSuggestionsWithTouchExploration=" 912 + mSupportsInlineSuggestionsWithTouchExploration 913 + " mSuppressesSpellChecker=" + mSuppressesSpellChecker 914 + " mShowInInputMethodPicker=" + mShowInInputMethodPicker 915 + " mSupportsStylusHandwriting=" + mSupportsStylusHandwriting 916 + " mSupportsConnectionlessStylusHandwriting=" 917 + mSupportsConnectionlessStylusHandwriting 918 + " mStylusHandwritingSettingsActivityAttr=" 919 + mStylusHandwritingSettingsActivityAttr); 920 pw.println(prefix + "mIsDefaultResId=0x" 921 + Integer.toHexString(mIsDefaultResId)); 922 pw.println(prefix + "Service:"); 923 mService.dump(pw, prefix + " "); 924 pw.println(prefix + "InputMethodSubtype array: count=" + mSubtypes.getCount()); 925 mSubtypes.dump(pw, prefix + " "); 926 } 927 928 @Override toString()929 public String toString() { 930 return "InputMethodInfo{" + mId 931 + ", settings: " + mSettingsActivityName 932 + ", languageSettings: " + mLanguageSettingsActivityName 933 + "}"; 934 } 935 936 /** 937 * Used to test whether the given parameter object is an 938 * {@link InputMethodInfo} and its Id is the same to this one. 939 * 940 * @return true if the given parameter object is an 941 * {@link InputMethodInfo} and its Id is the same to this one. 942 */ 943 @Override equals(@ullable Object o)944 public boolean equals(@Nullable Object o) { 945 if (o == this) return true; 946 if (o == null) return false; 947 948 if (!(o instanceof InputMethodInfo)) return false; 949 950 InputMethodInfo obj = (InputMethodInfo) o; 951 return mId.equals(obj.mId); 952 } 953 954 @Override hashCode()955 public int hashCode() { 956 return mId.hashCode(); 957 } 958 959 /** 960 * @hide 961 * @return {@code true} if the IME is a trusted system component (e.g. pre-installed) 962 */ isSystem()963 public boolean isSystem() { 964 return (mService.serviceInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0; 965 } 966 967 /** 968 * @hide 969 */ isAuxiliaryIme()970 public boolean isAuxiliaryIme() { 971 return mIsAuxIme; 972 } 973 974 /** 975 * @return true if this input method supports ways to switch to a next input method. 976 * @hide 977 */ supportsSwitchingToNextInputMethod()978 public boolean supportsSwitchingToNextInputMethod() { 979 return mSupportsSwitchingToNextInputMethod; 980 } 981 982 /** 983 * @return true if this input method supports inline suggestions. 984 * @hide 985 */ isInlineSuggestionsEnabled()986 public boolean isInlineSuggestionsEnabled() { 987 return mInlineSuggestionsEnabled; 988 } 989 990 /** 991 * Returns {@code true} if this input method supports inline suggestions when touch exploration 992 * is enabled. 993 * @hide 994 */ supportsInlineSuggestionsWithTouchExploration()995 public boolean supportsInlineSuggestionsWithTouchExploration() { 996 return mSupportsInlineSuggestionsWithTouchExploration; 997 } 998 999 /** 1000 * Return {@code true} if this input method suppresses spell checker. 1001 */ suppressesSpellChecker()1002 public boolean suppressesSpellChecker() { 1003 return mSuppressesSpellChecker; 1004 } 1005 1006 /** 1007 * Returns {@code true} if this input method should be shown in menus for selecting an Input 1008 * Method, such as the system Input Method Picker. This is {@code false} if the IME is intended 1009 * to be accessed programmatically. 1010 */ shouldShowInInputMethodPicker()1011 public boolean shouldShowInInputMethodPicker() { 1012 return mShowInInputMethodPicker; 1013 } 1014 1015 /** 1016 * Used to package this object into a {@link Parcel}. 1017 * 1018 * @param dest The {@link Parcel} to be written. 1019 * @param flags The flags used for parceling. 1020 */ 1021 @Override writeToParcel(Parcel dest, int flags)1022 public void writeToParcel(Parcel dest, int flags) { 1023 dest.writeString(mId); 1024 dest.writeString(mSettingsActivityName); 1025 dest.writeString8(mLanguageSettingsActivityName); 1026 dest.writeInt(mIsDefaultResId); 1027 dest.writeInt(mIsAuxIme ? 1 : 0); 1028 dest.writeInt(mSupportsSwitchingToNextInputMethod ? 1 : 0); 1029 dest.writeInt(mInlineSuggestionsEnabled ? 1 : 0); 1030 dest.writeInt(mSupportsInlineSuggestionsWithTouchExploration ? 1 : 0); 1031 dest.writeBoolean(mSuppressesSpellChecker); 1032 dest.writeBoolean(mShowInInputMethodPicker); 1033 dest.writeBoolean(mIsVrOnly); 1034 dest.writeBoolean(mIsVirtualDeviceOnly); 1035 mService.writeToParcel(dest, flags); 1036 mSubtypes.writeToParcel(dest); 1037 dest.writeInt(mHandledConfigChanges); 1038 dest.writeBoolean(mSupportsStylusHandwriting); 1039 dest.writeBoolean(mSupportsConnectionlessStylusHandwriting); 1040 dest.writeString8(mStylusHandwritingSettingsActivityAttr); 1041 } 1042 1043 /** 1044 * Used to make this class parcelable. 1045 */ 1046 public static final @android.annotation.NonNull Parcelable.Creator<InputMethodInfo> CREATOR 1047 = new Parcelable.Creator<InputMethodInfo>() { 1048 @Override 1049 public InputMethodInfo createFromParcel(Parcel source) { 1050 return new InputMethodInfo(source); 1051 } 1052 1053 @Override 1054 public InputMethodInfo[] newArray(int size) { 1055 return new InputMethodInfo[size]; 1056 } 1057 }; 1058 1059 @Override describeContents()1060 public int describeContents() { 1061 return 0; 1062 } 1063 } 1064