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