| /* |
| * Copyright (C) 2007-2008 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); you may not |
| * use this file except in compliance with the License. You may obtain a copy of |
| * the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| * License for the specific language governing permissions and limitations under |
| * the License. |
| */ |
| |
| package android.view.inputmethod; |
| |
| import android.annotation.FlaggedApi; |
| import android.annotation.NonNull; |
| import android.annotation.Nullable; |
| import android.annotation.SuppressLint; |
| import android.annotation.SystemApi; |
| import android.annotation.TestApi; |
| import android.compat.annotation.UnsupportedAppUsage; |
| import android.content.ComponentName; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.pm.ActivityInfo; |
| import android.content.pm.ApplicationInfo; |
| import android.content.pm.PackageManager; |
| import android.content.pm.PackageManager.NameNotFoundException; |
| import android.content.pm.ResolveInfo; |
| import android.content.pm.ServiceInfo; |
| import android.content.res.Configuration; |
| import android.content.res.Resources; |
| import android.content.res.Resources.NotFoundException; |
| import android.content.res.TypedArray; |
| import android.content.res.XmlResourceParser; |
| import android.graphics.drawable.Drawable; |
| import android.icu.util.ULocale; |
| import android.inputmethodservice.InputMethodService; |
| import android.os.Parcel; |
| import android.os.Parcelable; |
| import android.text.TextUtils; |
| import android.util.AttributeSet; |
| import android.util.Printer; |
| import android.util.Slog; |
| import android.util.Xml; |
| import android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder; |
| |
| import org.xmlpull.v1.XmlPullParser; |
| import org.xmlpull.v1.XmlPullParserException; |
| |
| import java.io.IOException; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.List; |
| |
| /** |
| * This class is used to specify meta information of an input method. |
| * |
| * <p>It should be defined in an XML resource file with an {@code <input-method>} element. |
| * For more information, see the guide to |
| * <a href="{@docRoot}guide/topics/text/creating-input-method.html"> |
| * Creating an Input Method</a>.</p> |
| * |
| * @see InputMethodSubtype |
| * |
| * @attr ref android.R.styleable#InputMethod_settingsActivity |
| * @attr ref android.R.styleable#InputMethod_isDefault |
| * @attr ref android.R.styleable#InputMethod_supportsSwitchingToNextInputMethod |
| * @attr ref android.R.styleable#InputMethod_supportsInlineSuggestions |
| * @attr ref android.R.styleable#InputMethod_supportsInlineSuggestionsWithTouchExploration |
| * @attr ref android.R.styleable#InputMethod_suppressesSpellChecker |
| * @attr ref android.R.styleable#InputMethod_showInInputMethodPicker |
| * @attr ref android.R.styleable#InputMethod_configChanges |
| */ |
| public final class InputMethodInfo implements Parcelable { |
| |
| /** |
| * {@link Intent#getAction() Intent action} for IME that |
| * {@link #supportsStylusHandwriting() supports stylus handwriting}. |
| * |
| * @see #createStylusHandwritingSettingsActivityIntent() |
| */ |
| public static final String ACTION_STYLUS_HANDWRITING_SETTINGS = |
| "android.view.inputmethod.action.STYLUS_HANDWRITING_SETTINGS"; |
| |
| /** |
| * {@link Intent#getAction() Intent action} for the IME language settings. |
| * |
| * @see #createImeLanguageSettingsActivityIntent() |
| */ |
| @FlaggedApi(android.view.inputmethod.Flags.FLAG_IME_SWITCHER_REVAMP) |
| public static final String ACTION_IME_LANGUAGE_SETTINGS = |
| "android.view.inputmethod.action.IME_LANGUAGE_SETTINGS"; |
| |
| /** |
| * Maximal length of a component name |
| * @hide |
| */ |
| @TestApi |
| public static final int COMPONENT_NAME_MAX_LENGTH = 1000; |
| |
| /** |
| * The maximum amount of IMEs that are loaded per package (in order). |
| * If a package contains more IMEs, they will be ignored and cannot be enabled. |
| * @hide |
| */ |
| @TestApi |
| @SuppressLint("MinMaxConstant") |
| public static final int MAX_IMES_PER_PACKAGE = 20; |
| |
| static final String TAG = "InputMethodInfo"; |
| |
| /** |
| * The Service that implements this input method component. |
| */ |
| final ResolveInfo mService; |
| |
| /** |
| * IME only supports VR mode. |
| */ |
| final boolean mIsVrOnly; |
| |
| /** |
| * IME only supports virtual devices. |
| */ |
| final boolean mIsVirtualDeviceOnly; |
| |
| /** |
| * The unique string Id to identify the input method. This is generated |
| * from the input method component. |
| */ |
| final String mId; |
| |
| /** |
| * The input method setting activity's name, used by the system settings to |
| * launch the setting activity of this input method. |
| */ |
| final String mSettingsActivityName; |
| |
| /** |
| * The input method language settings activity's name, used to |
| * launch the language settings activity of this input method. |
| */ |
| @Nullable |
| private final String mLanguageSettingsActivityName; |
| |
| /** |
| * The resource in the input method's .apk that holds a boolean indicating |
| * whether it should be considered the default input method for this |
| * system. This is a resource ID instead of the final value so that it |
| * can change based on the configuration (in particular locale). |
| */ |
| final int mIsDefaultResId; |
| |
| /** |
| * An array-like container of the subtypes. |
| */ |
| @UnsupportedAppUsage |
| private final InputMethodSubtypeArray mSubtypes; |
| |
| private final boolean mIsAuxIme; |
| |
| /** |
| * Caveat: mForceDefault must be false for production. This flag is only for test. |
| */ |
| private final boolean mForceDefault; |
| |
| /** |
| * The flag whether this IME supports ways to switch to a next input method (e.g. globe key.) |
| */ |
| private final boolean mSupportsSwitchingToNextInputMethod; |
| |
| /** |
| * The flag whether this IME supports inline suggestions. |
| */ |
| private final boolean mInlineSuggestionsEnabled; |
| |
| /** |
| * The flag whether this IME supports inline suggestions when touch exploration is enabled. |
| */ |
| private final boolean mSupportsInlineSuggestionsWithTouchExploration; |
| |
| /** |
| * The flag whether this IME suppresses spell checker. |
| */ |
| private final boolean mSuppressesSpellChecker; |
| |
| /** |
| * The flag whether this IME should be shown as an option in the IME picker. |
| */ |
| private final boolean mShowInInputMethodPicker; |
| |
| /** |
| * The flag for configurations IME assumes the responsibility for handling in |
| * {@link InputMethodService#onConfigurationChanged(Configuration)}}. |
| */ |
| private final int mHandledConfigChanges; |
| |
| /** |
| * The flag whether this IME supports Handwriting using stylus input. |
| */ |
| private final boolean mSupportsStylusHandwriting; |
| |
| /** The flag whether this IME supports connectionless stylus handwriting sessions. */ |
| private final boolean mSupportsConnectionlessStylusHandwriting; |
| |
| /** |
| * The stylus handwriting setting activity's name, used by the system settings to |
| * launch the stylus handwriting specific setting activity of this input method. |
| */ |
| private final String mStylusHandwritingSettingsActivityAttr; |
| |
| /** |
| * @param service the {@link ResolveInfo} corresponds in which the IME is implemented. |
| * @return a unique ID to be returned by {@link #getId()}. We have used |
| * {@link ComponentName#flattenToShortString()} for this purpose (and it is already |
| * unrealistic to switch to a different scheme as it is already implicitly assumed in |
| * many places). |
| * @hide |
| */ |
| public static String computeId(@NonNull ResolveInfo service) { |
| final ServiceInfo si = service.serviceInfo; |
| return new ComponentName(si.packageName, si.name).flattenToShortString(); |
| } |
| |
| /** |
| * Constructor. |
| * |
| * @param context The Context in which we are parsing the input method. |
| * @param service The ResolveInfo returned from the package manager about |
| * this input method's component. |
| */ |
| public InputMethodInfo(Context context, ResolveInfo service) |
| throws XmlPullParserException, IOException { |
| this(context, service, null); |
| } |
| |
| /** |
| * Constructor. |
| * |
| * @param context The Context in which we are parsing the input method. |
| * @param service The ResolveInfo returned from the package manager about |
| * this input method's component. |
| * @param additionalSubtypes additional subtypes being added to this InputMethodInfo |
| * @hide |
| */ |
| public InputMethodInfo(Context context, ResolveInfo service, |
| List<InputMethodSubtype> additionalSubtypes) |
| throws XmlPullParserException, IOException { |
| mService = service; |
| ServiceInfo si = service.serviceInfo; |
| mId = computeId(service); |
| boolean isAuxIme = true; |
| boolean supportsSwitchingToNextInputMethod = false; // false as default |
| boolean inlineSuggestionsEnabled = false; // false as default |
| boolean supportsInlineSuggestionsWithTouchExploration = false; // false as default |
| boolean suppressesSpellChecker = false; // false as default |
| boolean showInInputMethodPicker = true; // true as default |
| mForceDefault = false; |
| |
| PackageManager pm = context.getPackageManager(); |
| String settingsActivityComponent = null; |
| String languageSettingsActivityComponent = null; |
| String stylusHandwritingSettingsActivity = null; |
| boolean isVrOnly; |
| boolean isVirtualDeviceOnly; |
| int isDefaultResId = 0; |
| |
| XmlResourceParser parser = null; |
| final ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>(); |
| try { |
| parser = si.loadXmlMetaData(pm, InputMethod.SERVICE_META_DATA); |
| if (parser == null) { |
| throw new XmlPullParserException("No " |
| + InputMethod.SERVICE_META_DATA + " meta-data"); |
| } |
| |
| Resources res = pm.getResourcesForApplication(si.applicationInfo); |
| |
| AttributeSet attrs = Xml.asAttributeSet(parser); |
| |
| int type; |
| while ((type=parser.next()) != XmlPullParser.END_DOCUMENT |
| && type != XmlPullParser.START_TAG) { |
| } |
| |
| String nodeName = parser.getName(); |
| if (!"input-method".equals(nodeName)) { |
| throw new XmlPullParserException( |
| "Meta-data does not start with input-method tag"); |
| } |
| |
| TypedArray sa = res.obtainAttributes(attrs, |
| com.android.internal.R.styleable.InputMethod); |
| settingsActivityComponent = sa.getString( |
| com.android.internal.R.styleable.InputMethod_settingsActivity); |
| if (Flags.imeSwitcherRevamp()) { |
| languageSettingsActivityComponent = sa.getString( |
| com.android.internal.R.styleable.InputMethod_languageSettingsActivity); |
| } |
| if ((si.name != null && si.name.length() > COMPONENT_NAME_MAX_LENGTH) |
| || (settingsActivityComponent != null |
| && settingsActivityComponent.length() |
| > COMPONENT_NAME_MAX_LENGTH) |
| || (languageSettingsActivityComponent != null |
| && languageSettingsActivityComponent.length() |
| > COMPONENT_NAME_MAX_LENGTH)) { |
| throw new XmlPullParserException( |
| "Activity name exceeds maximum of 1000 characters"); |
| } |
| |
| isVrOnly = sa.getBoolean(com.android.internal.R.styleable.InputMethod_isVrOnly, false); |
| isVirtualDeviceOnly = sa.getBoolean( |
| com.android.internal.R.styleable.InputMethod_isVirtualDeviceOnly, false); |
| isDefaultResId = sa.getResourceId( |
| com.android.internal.R.styleable.InputMethod_isDefault, 0); |
| supportsSwitchingToNextInputMethod = sa.getBoolean( |
| com.android.internal.R.styleable.InputMethod_supportsSwitchingToNextInputMethod, |
| false); |
| inlineSuggestionsEnabled = sa.getBoolean( |
| com.android.internal.R.styleable.InputMethod_supportsInlineSuggestions, false); |
| supportsInlineSuggestionsWithTouchExploration = sa.getBoolean( |
| com.android.internal.R.styleable |
| .InputMethod_supportsInlineSuggestionsWithTouchExploration, false); |
| suppressesSpellChecker = sa.getBoolean( |
| com.android.internal.R.styleable.InputMethod_suppressesSpellChecker, false); |
| showInInputMethodPicker = sa.getBoolean( |
| com.android.internal.R.styleable.InputMethod_showInInputMethodPicker, true); |
| mHandledConfigChanges = sa.getInt( |
| com.android.internal.R.styleable.InputMethod_configChanges, 0); |
| mSupportsStylusHandwriting = sa.getBoolean( |
| com.android.internal.R.styleable.InputMethod_supportsStylusHandwriting, false); |
| mSupportsConnectionlessStylusHandwriting = sa.getBoolean( |
| com.android.internal.R.styleable |
| .InputMethod_supportsConnectionlessStylusHandwriting, false); |
| stylusHandwritingSettingsActivity = sa.getString( |
| com.android.internal.R.styleable.InputMethod_stylusHandwritingSettingsActivity); |
| sa.recycle(); |
| |
| final int depth = parser.getDepth(); |
| // Parse all subtypes |
| while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth) |
| && type != XmlPullParser.END_DOCUMENT) { |
| if (type == XmlPullParser.START_TAG) { |
| nodeName = parser.getName(); |
| if (!"subtype".equals(nodeName)) { |
| throw new XmlPullParserException( |
| "Meta-data in input-method does not start with subtype tag"); |
| } |
| final TypedArray a = res.obtainAttributes( |
| attrs, com.android.internal.R.styleable.InputMethod_Subtype); |
| String pkLanguageTag = a.getString(com.android.internal.R.styleable |
| .InputMethod_Subtype_physicalKeyboardHintLanguageTag); |
| String pkLayoutType = a.getString(com.android.internal.R.styleable |
| .InputMethod_Subtype_physicalKeyboardHintLayoutType); |
| final InputMethodSubtype subtype = new InputMethodSubtypeBuilder() |
| .setSubtypeNameResId(a.getResourceId(com.android.internal.R.styleable |
| .InputMethod_Subtype_label, 0)) |
| .setSubtypeIconResId(a.getResourceId(com.android.internal.R.styleable |
| .InputMethod_Subtype_icon, 0)) |
| .setPhysicalKeyboardHint( |
| pkLanguageTag == null ? null : new ULocale(pkLanguageTag), |
| pkLayoutType == null ? "" : pkLayoutType) |
| .setLanguageTag(a.getString(com.android.internal.R.styleable |
| .InputMethod_Subtype_languageTag)) |
| .setSubtypeLocale(a.getString(com.android.internal.R.styleable |
| .InputMethod_Subtype_imeSubtypeLocale)) |
| .setSubtypeMode(a.getString(com.android.internal.R.styleable |
| .InputMethod_Subtype_imeSubtypeMode)) |
| .setSubtypeExtraValue(a.getString(com.android.internal.R.styleable |
| .InputMethod_Subtype_imeSubtypeExtraValue)) |
| .setIsAuxiliary(a.getBoolean(com.android.internal.R.styleable |
| .InputMethod_Subtype_isAuxiliary, false)) |
| .setOverridesImplicitlyEnabledSubtype(a.getBoolean( |
| com.android.internal.R.styleable |
| .InputMethod_Subtype_overridesImplicitlyEnabledSubtype, false)) |
| .setSubtypeId(a.getInt(com.android.internal.R.styleable |
| .InputMethod_Subtype_subtypeId, 0 /* use Arrays.hashCode */)) |
| .setIsAsciiCapable(a.getBoolean(com.android.internal.R.styleable |
| .InputMethod_Subtype_isAsciiCapable, false)).build(); |
| a.recycle(); |
| if (!subtype.isAuxiliary()) { |
| isAuxIme = false; |
| } |
| subtypes.add(subtype); |
| } |
| } |
| } catch (NameNotFoundException | IndexOutOfBoundsException | NumberFormatException e) { |
| throw new XmlPullParserException( |
| "Unable to create context for: " + si.packageName); |
| } finally { |
| if (parser != null) parser.close(); |
| } |
| |
| if (subtypes.size() == 0) { |
| isAuxIme = false; |
| } |
| |
| if (additionalSubtypes != null) { |
| final int N = additionalSubtypes.size(); |
| for (int i = 0; i < N; ++i) { |
| final InputMethodSubtype subtype = additionalSubtypes.get(i); |
| if (!subtypes.contains(subtype)) { |
| subtypes.add(subtype); |
| } else { |
| Slog.w(TAG, "Duplicated subtype definition found: " |
| + subtype.getLocale() + ", " + subtype.getMode()); |
| } |
| } |
| } |
| mSubtypes = new InputMethodSubtypeArray(subtypes); |
| mSettingsActivityName = settingsActivityComponent; |
| mLanguageSettingsActivityName = languageSettingsActivityComponent; |
| mStylusHandwritingSettingsActivityAttr = stylusHandwritingSettingsActivity; |
| mIsDefaultResId = isDefaultResId; |
| mIsAuxIme = isAuxIme; |
| mSupportsSwitchingToNextInputMethod = supportsSwitchingToNextInputMethod; |
| mInlineSuggestionsEnabled = inlineSuggestionsEnabled; |
| mSupportsInlineSuggestionsWithTouchExploration = |
| supportsInlineSuggestionsWithTouchExploration; |
| mSuppressesSpellChecker = suppressesSpellChecker; |
| mShowInInputMethodPicker = showInInputMethodPicker; |
| mIsVrOnly = isVrOnly; |
| mIsVirtualDeviceOnly = isVirtualDeviceOnly; |
| } |
| |
| /** |
| * @hide |
| */ |
| public InputMethodInfo(InputMethodInfo source) { |
| this(source, Collections.emptyList()); |
| } |
| |
| /** |
| * @hide |
| */ |
| public InputMethodInfo(@NonNull InputMethodInfo source, |
| @NonNull List<InputMethodSubtype> additionalSubtypes) { |
| mId = source.mId; |
| mSettingsActivityName = source.mSettingsActivityName; |
| mLanguageSettingsActivityName = source.mLanguageSettingsActivityName; |
| mIsDefaultResId = source.mIsDefaultResId; |
| mIsAuxIme = source.mIsAuxIme; |
| mSupportsSwitchingToNextInputMethod = source.mSupportsSwitchingToNextInputMethod; |
| mInlineSuggestionsEnabled = source.mInlineSuggestionsEnabled; |
| mSupportsInlineSuggestionsWithTouchExploration = |
| source.mSupportsInlineSuggestionsWithTouchExploration; |
| mSuppressesSpellChecker = source.mSuppressesSpellChecker; |
| mShowInInputMethodPicker = source.mShowInInputMethodPicker; |
| mIsVrOnly = source.mIsVrOnly; |
| mIsVirtualDeviceOnly = source.mIsVirtualDeviceOnly; |
| mService = source.mService; |
| if (additionalSubtypes.isEmpty()) { |
| mSubtypes = source.mSubtypes; |
| } else { |
| final ArrayList<InputMethodSubtype> subtypes = source.mSubtypes.toList(); |
| final int additionalSubtypeCount = additionalSubtypes.size(); |
| for (int i = 0; i < additionalSubtypeCount; ++i) { |
| final InputMethodSubtype additionalSubtype = additionalSubtypes.get(i); |
| if (!subtypes.contains(additionalSubtype)) { |
| subtypes.add(additionalSubtype); |
| } |
| } |
| mSubtypes = new InputMethodSubtypeArray(subtypes); |
| } |
| mHandledConfigChanges = source.mHandledConfigChanges; |
| mSupportsStylusHandwriting = source.mSupportsStylusHandwriting; |
| mSupportsConnectionlessStylusHandwriting = source.mSupportsConnectionlessStylusHandwriting; |
| mForceDefault = source.mForceDefault; |
| mStylusHandwritingSettingsActivityAttr = source.mStylusHandwritingSettingsActivityAttr; |
| } |
| |
| InputMethodInfo(Parcel source) { |
| mId = source.readString(); |
| mSettingsActivityName = source.readString(); |
| mLanguageSettingsActivityName = source.readString8(); |
| mIsDefaultResId = source.readInt(); |
| mIsAuxIme = source.readInt() == 1; |
| mSupportsSwitchingToNextInputMethod = source.readInt() == 1; |
| mInlineSuggestionsEnabled = source.readInt() == 1; |
| mSupportsInlineSuggestionsWithTouchExploration = source.readInt() == 1; |
| mSuppressesSpellChecker = source.readBoolean(); |
| mShowInInputMethodPicker = source.readBoolean(); |
| mIsVrOnly = source.readBoolean(); |
| mIsVirtualDeviceOnly = source.readBoolean(); |
| mService = ResolveInfo.CREATOR.createFromParcel(source); |
| mSubtypes = new InputMethodSubtypeArray(source); |
| mHandledConfigChanges = source.readInt(); |
| mSupportsStylusHandwriting = source.readBoolean(); |
| mSupportsConnectionlessStylusHandwriting = source.readBoolean(); |
| mStylusHandwritingSettingsActivityAttr = source.readString8(); |
| mForceDefault = false; |
| } |
| |
| /** |
| * Temporary API for creating a built-in input method for test. |
| */ |
| public InputMethodInfo(String packageName, String className, |
| CharSequence label, String settingsActivity) { |
| this(buildFakeResolveInfo(packageName, className, label), false /* isAuxIme */, |
| settingsActivity, null /* languageSettingsActivity */, null /* subtypes */, |
| 0 /* isDefaultResId */, false /* forceDefault */, |
| true /* supportsSwitchingToNextInputMethod */, |
| false /* inlineSuggestionsEnabled */, false /* isVrOnly */, |
| false /* isVirtualDeviceOnly */, 0 /* handledConfigChanges */, |
| false /* supportsStylusHandwriting */, |
| false /* supportConnectionlessStylusHandwriting */, |
| null /* stylusHandwritingSettingsActivityAttr */, |
| false /* inlineSuggestionsEnabled */); |
| } |
| |
| /** |
| * Test API for creating a built-in input method to verify stylus handwriting. |
| * @hide |
| */ |
| @TestApi |
| public InputMethodInfo(@NonNull String packageName, @NonNull String className, |
| @NonNull CharSequence label, @NonNull String settingsActivity, |
| boolean supportStylusHandwriting, |
| @NonNull String stylusHandwritingSettingsActivityAttr) { |
| this(buildFakeResolveInfo(packageName, className, label), false /* isAuxIme */, |
| settingsActivity, null /* languageSettingsActivity */, |
| null /* subtypes */, 0 /* isDefaultResId */, |
| false /* forceDefault */, true /* supportsSwitchingToNextInputMethod */, |
| false /* inlineSuggestionsEnabled */, false /* isVrOnly */, |
| false /* isVirtualDeviceOnly */, 0 /* handledConfigChanges */, |
| supportStylusHandwriting, false /* supportConnectionlessStylusHandwriting */, |
| stylusHandwritingSettingsActivityAttr, false /* inlineSuggestionsEnabled */); |
| } |
| |
| /** |
| * Test API for creating a built-in input method to verify stylus handwriting. |
| * @hide |
| */ |
| @TestApi |
| public InputMethodInfo(@NonNull String packageName, @NonNull String className, |
| @NonNull CharSequence label, @NonNull String settingsActivity, |
| @NonNull String languageSettingsActivity, boolean supportStylusHandwriting, |
| @NonNull String stylusHandwritingSettingsActivityAttr) { |
| this(buildFakeResolveInfo(packageName, className, label), false /* isAuxIme */, |
| settingsActivity, languageSettingsActivity, null /* subtypes */, |
| 0 /* isDefaultResId */, false /* forceDefault */, |
| true /* supportsSwitchingToNextInputMethod */, |
| false /* inlineSuggestionsEnabled */, false /* isVrOnly */, |
| false /* isVirtualDeviceOnly */, 0 /* handledConfigChanges */, |
| supportStylusHandwriting, false /* supportConnectionlessStylusHandwriting */, |
| stylusHandwritingSettingsActivityAttr, false /* inlineSuggestionsEnabled */); |
| } |
| |
| /** |
| * Test API for creating a built-in input method to verify stylus handwriting. |
| * @hide |
| */ |
| @TestApi |
| @FlaggedApi(Flags.FLAG_CONNECTIONLESS_HANDWRITING) |
| public InputMethodInfo(@NonNull String packageName, @NonNull String className, |
| @NonNull CharSequence label, @NonNull String settingsActivity, |
| @NonNull String languageSettingsActivity, boolean supportStylusHandwriting, |
| boolean supportConnectionlessStylusHandwriting, |
| @NonNull String stylusHandwritingSettingsActivityAttr) { |
| this(buildFakeResolveInfo(packageName, className, label), false /* isAuxIme */, |
| settingsActivity, languageSettingsActivity, null /* subtypes */, |
| 0 /* isDefaultResId */, false /* forceDefault */, |
| true /* supportsSwitchingToNextInputMethod */, |
| false /* inlineSuggestionsEnabled */, false /* isVrOnly */, |
| false /* isVirtualDeviceOnly */, 0 /* handledConfigChanges */, |
| supportStylusHandwriting, supportConnectionlessStylusHandwriting, |
| stylusHandwritingSettingsActivityAttr, false /* inlineSuggestionsEnabled */); |
| } |
| |
| /** |
| * Temporary API for creating a built-in input method for test. |
| * @hide |
| */ |
| @TestApi |
| public InputMethodInfo(@NonNull String packageName, @NonNull String className, |
| @NonNull CharSequence label, @NonNull String settingsActivity, |
| int handledConfigChanges) { |
| this(buildFakeResolveInfo(packageName, className, label), false /* isAuxIme */, |
| settingsActivity, null /* languageSettingsActivity */, null /* subtypes */, |
| 0 /* isDefaultResId */, false /* forceDefault */, |
| true /* supportsSwitchingToNextInputMethod */, |
| false /* inlineSuggestionsEnabled */, false /* isVrOnly */, |
| false /* isVirtualDeviceOnly */, handledConfigChanges, |
| false /* supportsStylusHandwriting */, |
| false /* supportConnectionlessStylusHandwriting */, |
| null /* stylusHandwritingSettingsActivityAttr */, |
| false /* inlineSuggestionsEnabled */); |
| } |
| |
| /** |
| * Temporary API for creating a built-in input method for test. |
| * @hide |
| */ |
| public InputMethodInfo(ResolveInfo ri, boolean isAuxIme, |
| String settingsActivity, List<InputMethodSubtype> subtypes, int isDefaultResId, |
| boolean forceDefault) { |
| this(ri, isAuxIme, settingsActivity, null /* languageSettingsActivity */, subtypes, |
| isDefaultResId, forceDefault, |
| true /* supportsSwitchingToNextInputMethod */, false /* inlineSuggestionsEnabled */, |
| false /* isVrOnly */, false /* isVirtualDeviceOnly */, 0 /* handledconfigChanges */, |
| false /* supportsStylusHandwriting */, |
| false /* supportConnectionlessStylusHandwriting */, |
| null /* stylusHandwritingSettingsActivityAttr */, |
| false /* inlineSuggestionsEnabled */); |
| } |
| |
| /** |
| * Temporary API for creating a built-in input method for test. |
| * @hide |
| */ |
| public InputMethodInfo(ResolveInfo ri, boolean isAuxIme, String settingsActivity, |
| List<InputMethodSubtype> subtypes, int isDefaultResId, boolean forceDefault, |
| boolean supportsSwitchingToNextInputMethod, boolean isVrOnly) { |
| this(ri, isAuxIme, settingsActivity, null /* languageSettingsActivity */, subtypes, |
| isDefaultResId, forceDefault, |
| supportsSwitchingToNextInputMethod, false /* inlineSuggestionsEnabled */, isVrOnly, |
| false /* isVirtualDeviceOnly */, |
| 0 /* handledConfigChanges */, false /* supportsStylusHandwriting */, |
| false /* supportConnectionlessStylusHandwriting */, |
| null /* stylusHandwritingSettingsActivityAttr */, |
| false /* inlineSuggestionsEnabled */); |
| } |
| |
| /** |
| * Temporary API for creating a built-in input method for test. |
| * @hide |
| */ |
| public 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) { |
| final ServiceInfo si = ri.serviceInfo; |
| mService = ri; |
| mId = new ComponentName(si.packageName, si.name).flattenToShortString(); |
| mSettingsActivityName = settingsActivity; |
| mLanguageSettingsActivityName = languageSettingsActivity; |
| mIsDefaultResId = isDefaultResId; |
| mIsAuxIme = isAuxIme; |
| mSubtypes = new InputMethodSubtypeArray(subtypes); |
| mForceDefault = forceDefault; |
| mSupportsSwitchingToNextInputMethod = supportsSwitchingToNextInputMethod; |
| mInlineSuggestionsEnabled = inlineSuggestionsEnabled; |
| mSupportsInlineSuggestionsWithTouchExploration = |
| supportsInlineSuggestionsWithTouchExploration; |
| mSuppressesSpellChecker = false; |
| mShowInInputMethodPicker = true; |
| mIsVrOnly = isVrOnly; |
| mIsVirtualDeviceOnly = isVirtualDeviceOnly; |
| mHandledConfigChanges = handledConfigChanges; |
| mSupportsStylusHandwriting = supportsStylusHandwriting; |
| mSupportsConnectionlessStylusHandwriting = supportsConnectionlessStylusHandwriting; |
| mStylusHandwritingSettingsActivityAttr = stylusHandwritingSettingsActivityAttr; |
| } |
| |
| private static ResolveInfo buildFakeResolveInfo(String packageName, String className, |
| CharSequence label) { |
| ResolveInfo ri = new ResolveInfo(); |
| ServiceInfo si = new ServiceInfo(); |
| ApplicationInfo ai = new ApplicationInfo(); |
| ai.packageName = packageName; |
| ai.enabled = true; |
| si.applicationInfo = ai; |
| si.enabled = true; |
| si.packageName = packageName; |
| si.name = className; |
| si.exported = true; |
| si.nonLocalizedLabel = label; |
| ri.serviceInfo = si; |
| return ri; |
| } |
| |
| /** |
| * @return a unique ID for this input method, which is guaranteed to be the same as the result |
| * of {@code getComponent().flattenToShortString()}. |
| * @see ComponentName#unflattenFromString(String) |
| */ |
| public String getId() { |
| return mId; |
| } |
| |
| /** |
| * Return the .apk package that implements this input method. |
| */ |
| public String getPackageName() { |
| return mService.serviceInfo.packageName; |
| } |
| |
| /** |
| * Return the class name of the service component that implements |
| * this input method. |
| */ |
| public String getServiceName() { |
| return mService.serviceInfo.name; |
| } |
| |
| /** |
| * Return the raw information about the Service implementing this |
| * input method. Do not modify the returned object. |
| */ |
| public ServiceInfo getServiceInfo() { |
| return mService.serviceInfo; |
| } |
| |
| /** |
| * Return the component of the service that implements this input |
| * method. |
| */ |
| public ComponentName getComponent() { |
| return new ComponentName(mService.serviceInfo.packageName, |
| mService.serviceInfo.name); |
| } |
| |
| /** |
| * Load the user-displayed label for this input method. |
| * |
| * @param pm Supply a PackageManager used to load the input method's |
| * resources. |
| */ |
| public CharSequence loadLabel(PackageManager pm) { |
| return mService.loadLabel(pm); |
| } |
| |
| /** |
| * Load the user-displayed icon for this input method. |
| * |
| * @param pm Supply a PackageManager used to load the input method's |
| * resources. |
| */ |
| public Drawable loadIcon(PackageManager pm) { |
| return mService.loadIcon(pm); |
| } |
| |
| /** |
| * Return the class name of an activity that provides a settings UI for |
| * the input method. You can launch this activity be starting it with |
| * an {@link android.content.Intent} whose action is MAIN and with an |
| * explicit {@link android.content.ComponentName} |
| * composed of {@link #getPackageName} and the class name returned here. |
| * |
| * <p>A null will be returned if there is no settings activity associated |
| * with the input method.</p> |
| * @see #createStylusHandwritingSettingsActivityIntent() |
| */ |
| public String getSettingsActivity() { |
| return mSettingsActivityName; |
| } |
| |
| /** |
| * Returns true if IME supports VR mode only. |
| * @hide |
| */ |
| public boolean isVrOnly() { |
| return mIsVrOnly; |
| } |
| |
| /** |
| * Returns true if IME supports only virtual devices. |
| * @hide |
| */ |
| @FlaggedApi(android.companion.virtual.flags.Flags.FLAG_VDM_CUSTOM_IME) |
| @SystemApi |
| public boolean isVirtualDeviceOnly() { |
| return mIsVirtualDeviceOnly; |
| } |
| |
| /** |
| * Return the count of the subtypes of Input Method. |
| */ |
| public int getSubtypeCount() { |
| return mSubtypes.getCount(); |
| } |
| |
| /** |
| * Return the Input Method's subtype at the specified index. |
| * |
| * @param index the index of the subtype to return. |
| */ |
| public InputMethodSubtype getSubtypeAt(int index) { |
| return mSubtypes.get(index); |
| } |
| |
| /** |
| * Return the resource identifier of a resource inside of this input |
| * method's .apk that determines whether it should be considered a |
| * default input method for the system. |
| */ |
| public int getIsDefaultResourceId() { |
| return mIsDefaultResId; |
| } |
| |
| /** |
| * Return whether or not this ime is a default ime or not. |
| * @hide |
| */ |
| @UnsupportedAppUsage |
| public boolean isDefault(Context context) { |
| if (mForceDefault) { |
| return true; |
| } |
| try { |
| if (getIsDefaultResourceId() == 0) { |
| return false; |
| } |
| final Resources res = context.createPackageContext(getPackageName(), 0).getResources(); |
| return res.getBoolean(getIsDefaultResourceId()); |
| } catch (NameNotFoundException | NotFoundException e) { |
| return false; |
| } |
| } |
| |
| /** |
| * Returns the bit mask of kinds of configuration changes that this IME |
| * can handle itself (without being restarted by the system). |
| * |
| * @attr ref android.R.styleable#InputMethod_configChanges |
| */ |
| @ActivityInfo.Config |
| public int getConfigChanges() { |
| return mHandledConfigChanges; |
| } |
| |
| /** |
| * Returns if IME supports handwriting using stylus input. |
| * @attr ref android.R.styleable#InputMethod_supportsStylusHandwriting |
| * @see #createStylusHandwritingSettingsActivityIntent() |
| */ |
| public boolean supportsStylusHandwriting() { |
| return mSupportsStylusHandwriting; |
| } |
| |
| /** |
| * Returns whether the IME supports connectionless stylus handwriting sessions. |
| * |
| * @attr ref android.R.styleable#InputMethod_supportsConnectionlessStylusHandwriting |
| */ |
| @FlaggedApi(Flags.FLAG_CONNECTIONLESS_HANDWRITING) |
| public boolean supportsConnectionlessStylusHandwriting() { |
| return mSupportsConnectionlessStylusHandwriting; |
| } |
| |
| /** |
| * Returns {@link Intent} for stylus handwriting settings activity with |
| * {@link Intent#getAction() Intent action} {@link #ACTION_STYLUS_HANDWRITING_SETTINGS} |
| * if IME {@link #supportsStylusHandwriting() supports stylus handwriting}, else |
| * <code>null</code> if there are no associated settings for stylus handwriting / handwriting |
| * is not supported or if |
| * {@link android.R.styleable#InputMethod_stylusHandwritingSettingsActivity} is not defined. |
| * |
| * <p>To launch stylus settings, use this method to get the {@link android.content.Intent} to |
| * launch the stylus handwriting settings activity.</p> |
| * <p>e.g.<pre><code>startActivity(createStylusHandwritingSettingsActivityIntent());</code> |
| * </pre></p> |
| * |
| * @attr ref R.styleable#InputMethod_stylusHandwritingSettingsActivity |
| * @see #getSettingsActivity() |
| * @see #supportsStylusHandwriting() |
| */ |
| @Nullable |
| public Intent createStylusHandwritingSettingsActivityIntent() { |
| if (TextUtils.isEmpty(mStylusHandwritingSettingsActivityAttr) |
| || !mSupportsStylusHandwriting) { |
| return null; |
| } |
| // TODO(b/210039666): consider returning null if component is not enabled. |
| return new Intent(ACTION_STYLUS_HANDWRITING_SETTINGS).setComponent( |
| new ComponentName(getServiceInfo().packageName, |
| mStylusHandwritingSettingsActivityAttr)); |
| } |
| |
| /** |
| * Returns {@link Intent} for IME language settings activity with |
| * {@link Intent#getAction() Intent action} {@link #ACTION_IME_LANGUAGE_SETTINGS}, |
| * else <code>null</code> if |
| * {@link android.R.styleable#InputMethod_languageSettingsActivity} is not defined. |
| * |
| * <p>To launch IME language settings, use this method to get the {@link Intent} to launch |
| * the IME language settings activity.</p> |
| * <p>e.g.<pre><code>startActivity(createImeLanguageSettingsActivityIntent());</code></pre></p> |
| * |
| * @attr ref R.styleable#InputMethod_languageSettingsActivity |
| */ |
| @FlaggedApi(android.view.inputmethod.Flags.FLAG_IME_SWITCHER_REVAMP) |
| @Nullable |
| public Intent createImeLanguageSettingsActivityIntent() { |
| if (TextUtils.isEmpty(mLanguageSettingsActivityName)) { |
| return null; |
| } |
| return new Intent(ACTION_IME_LANGUAGE_SETTINGS).setComponent( |
| new ComponentName(getServiceInfo().packageName, |
| mLanguageSettingsActivityName) |
| ); |
| } |
| |
| public void dump(Printer pw, String prefix) { |
| pw.println(prefix + "mId=" + mId |
| + " mSettingsActivityName=" + mSettingsActivityName |
| + " mLanguageSettingsActivityName=" + mLanguageSettingsActivityName |
| + " mIsVrOnly=" + mIsVrOnly |
| + " mIsVirtualDeviceOnly=" + mIsVirtualDeviceOnly |
| + " mSupportsSwitchingToNextInputMethod=" + mSupportsSwitchingToNextInputMethod |
| + " mInlineSuggestionsEnabled=" + mInlineSuggestionsEnabled |
| + " mSupportsInlineSuggestionsWithTouchExploration=" |
| + mSupportsInlineSuggestionsWithTouchExploration |
| + " mSuppressesSpellChecker=" + mSuppressesSpellChecker |
| + " mShowInInputMethodPicker=" + mShowInInputMethodPicker |
| + " mSupportsStylusHandwriting=" + mSupportsStylusHandwriting |
| + " mSupportsConnectionlessStylusHandwriting=" |
| + mSupportsConnectionlessStylusHandwriting |
| + " mStylusHandwritingSettingsActivityAttr=" |
| + mStylusHandwritingSettingsActivityAttr); |
| pw.println(prefix + "mIsDefaultResId=0x" |
| + Integer.toHexString(mIsDefaultResId)); |
| pw.println(prefix + "Service:"); |
| mService.dump(pw, prefix + " "); |
| pw.println(prefix + "InputMethodSubtype array: count=" + mSubtypes.getCount()); |
| mSubtypes.dump(pw, prefix + " "); |
| } |
| |
| @Override |
| public String toString() { |
| return "InputMethodInfo{" + mId |
| + ", settings: " + mSettingsActivityName |
| + ", languageSettings: " + mLanguageSettingsActivityName |
| + "}"; |
| } |
| |
| /** |
| * Used to test whether the given parameter object is an |
| * {@link InputMethodInfo} and its Id is the same to this one. |
| * |
| * @return true if the given parameter object is an |
| * {@link InputMethodInfo} and its Id is the same to this one. |
| */ |
| @Override |
| public boolean equals(@Nullable Object o) { |
| if (o == this) return true; |
| if (o == null) return false; |
| |
| if (!(o instanceof InputMethodInfo)) return false; |
| |
| InputMethodInfo obj = (InputMethodInfo) o; |
| return mId.equals(obj.mId); |
| } |
| |
| @Override |
| public int hashCode() { |
| return mId.hashCode(); |
| } |
| |
| /** |
| * @hide |
| * @return {@code true} if the IME is a trusted system component (e.g. pre-installed) |
| */ |
| public boolean isSystem() { |
| return (mService.serviceInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0; |
| } |
| |
| /** |
| * @hide |
| */ |
| public boolean isAuxiliaryIme() { |
| return mIsAuxIme; |
| } |
| |
| /** |
| * @return true if this input method supports ways to switch to a next input method. |
| * @hide |
| */ |
| public boolean supportsSwitchingToNextInputMethod() { |
| return mSupportsSwitchingToNextInputMethod; |
| } |
| |
| /** |
| * @return true if this input method supports inline suggestions. |
| * @hide |
| */ |
| public boolean isInlineSuggestionsEnabled() { |
| return mInlineSuggestionsEnabled; |
| } |
| |
| /** |
| * Returns {@code true} if this input method supports inline suggestions when touch exploration |
| * is enabled. |
| * @hide |
| */ |
| public boolean supportsInlineSuggestionsWithTouchExploration() { |
| return mSupportsInlineSuggestionsWithTouchExploration; |
| } |
| |
| /** |
| * Return {@code true} if this input method suppresses spell checker. |
| */ |
| public boolean suppressesSpellChecker() { |
| return mSuppressesSpellChecker; |
| } |
| |
| /** |
| * Returns {@code true} if this input method should be shown in menus for selecting an Input |
| * Method, such as the system Input Method Picker. This is {@code false} if the IME is intended |
| * to be accessed programmatically. |
| */ |
| public boolean shouldShowInInputMethodPicker() { |
| return mShowInInputMethodPicker; |
| } |
| |
| /** |
| * Used to package this object into a {@link Parcel}. |
| * |
| * @param dest The {@link Parcel} to be written. |
| * @param flags The flags used for parceling. |
| */ |
| @Override |
| public void writeToParcel(Parcel dest, int flags) { |
| dest.writeString(mId); |
| dest.writeString(mSettingsActivityName); |
| dest.writeString8(mLanguageSettingsActivityName); |
| dest.writeInt(mIsDefaultResId); |
| dest.writeInt(mIsAuxIme ? 1 : 0); |
| dest.writeInt(mSupportsSwitchingToNextInputMethod ? 1 : 0); |
| dest.writeInt(mInlineSuggestionsEnabled ? 1 : 0); |
| dest.writeInt(mSupportsInlineSuggestionsWithTouchExploration ? 1 : 0); |
| dest.writeBoolean(mSuppressesSpellChecker); |
| dest.writeBoolean(mShowInInputMethodPicker); |
| dest.writeBoolean(mIsVrOnly); |
| dest.writeBoolean(mIsVirtualDeviceOnly); |
| mService.writeToParcel(dest, flags); |
| mSubtypes.writeToParcel(dest); |
| dest.writeInt(mHandledConfigChanges); |
| dest.writeBoolean(mSupportsStylusHandwriting); |
| dest.writeBoolean(mSupportsConnectionlessStylusHandwriting); |
| dest.writeString8(mStylusHandwritingSettingsActivityAttr); |
| } |
| |
| /** |
| * Used to make this class parcelable. |
| */ |
| public static final @android.annotation.NonNull Parcelable.Creator<InputMethodInfo> CREATOR |
| = new Parcelable.Creator<InputMethodInfo>() { |
| @Override |
| public InputMethodInfo createFromParcel(Parcel source) { |
| return new InputMethodInfo(source); |
| } |
| |
| @Override |
| public InputMethodInfo[] newArray(int size) { |
| return new InputMethodInfo[size]; |
| } |
| }; |
| |
| @Override |
| public int describeContents() { |
| return 0; |
| } |
| } |