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