• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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_API)
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.imeSwitcherRevampApi()) {
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     @SystemApi
isVirtualDeviceOnly()770     public boolean isVirtualDeviceOnly() {
771         return mIsVirtualDeviceOnly;
772     }
773 
774     /**
775      * Return the count of the subtypes of Input Method.
776      */
getSubtypeCount()777     public int getSubtypeCount() {
778         return mSubtypes.getCount();
779     }
780 
781     /**
782      * Return the Input Method's subtype at the specified index.
783      *
784      * @param index the index of the subtype to return.
785      */
getSubtypeAt(int index)786     public InputMethodSubtype getSubtypeAt(int index) {
787         return mSubtypes.get(index);
788     }
789 
790     /**
791      * Return the resource identifier of a resource inside of this input
792      * method's .apk that determines whether it should be considered a
793      * default input method for the system.
794      */
getIsDefaultResourceId()795     public int getIsDefaultResourceId() {
796         return mIsDefaultResId;
797     }
798 
799     /**
800      * Return whether or not this ime is a default ime or not.
801      * @hide
802      */
803     @UnsupportedAppUsage
isDefault(Context context)804     public boolean isDefault(Context context) {
805         if (mForceDefault) {
806             return true;
807         }
808         try {
809             if (getIsDefaultResourceId() == 0) {
810                 return false;
811             }
812             final Resources res = context.createPackageContext(getPackageName(), 0).getResources();
813             return res.getBoolean(getIsDefaultResourceId());
814         } catch (NameNotFoundException | NotFoundException e) {
815             return false;
816         }
817     }
818 
819     /**
820      * Returns the bit mask of kinds of configuration changes that this IME
821      * can handle itself (without being restarted by the system).
822      *
823      * @attr ref android.R.styleable#InputMethod_configChanges
824      */
825     @ActivityInfo.Config
getConfigChanges()826     public int getConfigChanges() {
827         return mHandledConfigChanges;
828     }
829 
830     /**
831      * Returns if IME supports handwriting using stylus input.
832      * @attr ref android.R.styleable#InputMethod_supportsStylusHandwriting
833      * @see #createStylusHandwritingSettingsActivityIntent()
834      */
supportsStylusHandwriting()835     public boolean supportsStylusHandwriting() {
836         return mSupportsStylusHandwriting;
837     }
838 
839     /**
840      * Returns whether the IME supports connectionless stylus handwriting sessions.
841      *
842      * @attr ref android.R.styleable#InputMethod_supportsConnectionlessStylusHandwriting
843      */
844     @FlaggedApi(Flags.FLAG_CONNECTIONLESS_HANDWRITING)
supportsConnectionlessStylusHandwriting()845     public boolean supportsConnectionlessStylusHandwriting() {
846         return mSupportsConnectionlessStylusHandwriting;
847     }
848 
849     /**
850      * Returns {@link Intent} for stylus handwriting settings activity with
851      * {@link Intent#getAction() Intent action} {@link #ACTION_STYLUS_HANDWRITING_SETTINGS}
852      * if IME {@link #supportsStylusHandwriting() supports stylus handwriting}, else
853      * <code>null</code> if there are no associated settings for stylus handwriting / handwriting
854      * is not supported or if
855      * {@link android.R.styleable#InputMethod_stylusHandwritingSettingsActivity} is not defined.
856      *
857      * <p>To launch stylus settings, use this method to get the {@link android.content.Intent} to
858      * launch the stylus handwriting settings activity.</p>
859      * <p>e.g.<pre><code>startActivity(createStylusHandwritingSettingsActivityIntent());</code>
860      * </pre></p>
861      *
862      * @attr ref android.R.styleable#InputMethod_stylusHandwritingSettingsActivity
863      * @see #getSettingsActivity()
864      * @see #supportsStylusHandwriting()
865      */
866     @Nullable
createStylusHandwritingSettingsActivityIntent()867     public Intent createStylusHandwritingSettingsActivityIntent() {
868         if (TextUtils.isEmpty(mStylusHandwritingSettingsActivityAttr)
869                 || !mSupportsStylusHandwriting) {
870             return null;
871         }
872         // TODO(b/210039666): consider returning null if component is not enabled.
873         return new Intent(ACTION_STYLUS_HANDWRITING_SETTINGS).setComponent(
874                 new ComponentName(getServiceInfo().packageName,
875                         mStylusHandwritingSettingsActivityAttr));
876     }
877 
878     /**
879      * Returns {@link Intent} for IME language settings activity with
880      * {@link Intent#getAction() Intent action} {@link #ACTION_IME_LANGUAGE_SETTINGS}. If
881      * {@link android.R.styleable#InputMethod_languageSettingsActivity} is not defined, tries to
882      * fall back to the IME general settings activity. If
883      * {@link android.R.styleable#InputMethod_settingsActivity} is also not defined,
884      * returns {code null}.
885      *
886      * <p>To launch IME language settings, use this method to get the {@link Intent} to launch
887      * the IME language settings activity.</p>
888      * <p>e.g.<pre><code>startActivity(createImeLanguageSettingsActivityIntent());</code></pre></p>
889      *
890      * @attr ref android.R.styleable#InputMethod_languageSettingsActivity
891      * @attr ref android.R.styleable#InputMethod_settingsActivity
892      */
893     @FlaggedApi(android.view.inputmethod.Flags.FLAG_IME_SWITCHER_REVAMP_API)
894     @Nullable
createImeLanguageSettingsActivityIntent()895     public Intent createImeLanguageSettingsActivityIntent() {
896         final var activityName = !TextUtils.isEmpty(mLanguageSettingsActivityName)
897                 ? mLanguageSettingsActivityName : mSettingsActivityName;
898         if (TextUtils.isEmpty(activityName)) {
899             return null;
900         }
901         return new Intent(ACTION_IME_LANGUAGE_SETTINGS).setComponent(
902                 new ComponentName(getServiceInfo().packageName, activityName)
903         );
904     }
905 
dump(Printer pw, String prefix)906     public void dump(Printer pw, String prefix) {
907         pw.println(prefix + "mId=" + mId
908                 + " mSettingsActivityName=" + mSettingsActivityName
909                 + " mLanguageSettingsActivityName=" + mLanguageSettingsActivityName
910                 + " mIsVrOnly=" + mIsVrOnly
911                 + " mIsVirtualDeviceOnly=" + mIsVirtualDeviceOnly
912                 + " mSupportsSwitchingToNextInputMethod=" + mSupportsSwitchingToNextInputMethod
913                 + " mInlineSuggestionsEnabled=" + mInlineSuggestionsEnabled
914                 + " mSupportsInlineSuggestionsWithTouchExploration="
915                 + mSupportsInlineSuggestionsWithTouchExploration
916                 + " mSuppressesSpellChecker=" + mSuppressesSpellChecker
917                 + " mShowInInputMethodPicker=" + mShowInInputMethodPicker
918                 + " mSupportsStylusHandwriting=" + mSupportsStylusHandwriting
919                 + " mSupportsConnectionlessStylusHandwriting="
920                 + mSupportsConnectionlessStylusHandwriting
921                 + " mStylusHandwritingSettingsActivityAttr="
922                         + mStylusHandwritingSettingsActivityAttr);
923         pw.println(prefix + "mIsDefaultResId=0x"
924                 + Integer.toHexString(mIsDefaultResId));
925         pw.println(prefix + "Service:");
926         mService.dump(pw, prefix + "  ");
927         pw.println(prefix + "InputMethodSubtype array: count=" + mSubtypes.getCount());
928         mSubtypes.dump(pw, prefix + "  ");
929     }
930 
931     @Override
toString()932     public String toString() {
933         return "InputMethodInfo{" + mId
934                 + ", settings: " + mSettingsActivityName
935                 + ", languageSettings: " + mLanguageSettingsActivityName
936                 + "}";
937     }
938 
939     /**
940      * Used to test whether the given parameter object is an
941      * {@link InputMethodInfo} and its Id is the same to this one.
942      *
943      * @return true if the given parameter object is an
944      *         {@link InputMethodInfo} and its Id is the same to this one.
945      */
946     @Override
equals(@ullable Object o)947     public boolean equals(@Nullable Object o) {
948         if (o == this) return true;
949         if (o == null) return false;
950 
951         if (!(o instanceof InputMethodInfo)) return false;
952 
953         InputMethodInfo obj = (InputMethodInfo) o;
954         return mId.equals(obj.mId);
955     }
956 
957     @Override
hashCode()958     public int hashCode() {
959         return mId.hashCode();
960     }
961 
962     /**
963      * @hide
964      * @return {@code true} if the IME is a trusted system component (e.g. pre-installed)
965      */
isSystem()966     public boolean isSystem() {
967         return (mService.serviceInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
968     }
969 
970     /**
971      * @hide
972      */
isAuxiliaryIme()973     public boolean isAuxiliaryIme() {
974         return mIsAuxIme;
975     }
976 
977     /**
978      * @return true if this input method supports ways to switch to a next input method.
979      * @hide
980      */
supportsSwitchingToNextInputMethod()981     public boolean supportsSwitchingToNextInputMethod() {
982         return mSupportsSwitchingToNextInputMethod;
983     }
984 
985     /**
986      * @return true if this input method supports inline suggestions.
987      * @hide
988      */
isInlineSuggestionsEnabled()989     public boolean isInlineSuggestionsEnabled() {
990         return mInlineSuggestionsEnabled;
991     }
992 
993     /**
994      * Returns {@code true} if this input method supports inline suggestions when touch exploration
995      * is enabled.
996      * @hide
997      */
supportsInlineSuggestionsWithTouchExploration()998     public boolean supportsInlineSuggestionsWithTouchExploration() {
999         return mSupportsInlineSuggestionsWithTouchExploration;
1000     }
1001 
1002     /**
1003      * Return {@code true} if this input method suppresses spell checker.
1004      */
suppressesSpellChecker()1005     public boolean suppressesSpellChecker() {
1006         return mSuppressesSpellChecker;
1007     }
1008 
1009     /**
1010      * Returns {@code true} if this input method should be shown in menus for selecting an Input
1011      * Method, such as the system Input Method Picker. This is {@code false} if the IME is intended
1012      * to be accessed programmatically.
1013      */
shouldShowInInputMethodPicker()1014     public boolean shouldShowInInputMethodPicker() {
1015         return mShowInInputMethodPicker;
1016     }
1017 
1018     /**
1019      * Used to package this object into a {@link Parcel}.
1020      *
1021      * @param dest The {@link Parcel} to be written.
1022      * @param flags The flags used for parceling.
1023      */
1024     @Override
writeToParcel(Parcel dest, int flags)1025     public void writeToParcel(Parcel dest, int flags) {
1026         dest.writeString(mId);
1027         dest.writeString(mSettingsActivityName);
1028         dest.writeString8(mLanguageSettingsActivityName);
1029         dest.writeInt(mIsDefaultResId);
1030         dest.writeInt(mIsAuxIme ? 1 : 0);
1031         dest.writeInt(mSupportsSwitchingToNextInputMethod ? 1 : 0);
1032         dest.writeInt(mInlineSuggestionsEnabled ? 1 : 0);
1033         dest.writeInt(mSupportsInlineSuggestionsWithTouchExploration ? 1 : 0);
1034         dest.writeBoolean(mSuppressesSpellChecker);
1035         dest.writeBoolean(mShowInInputMethodPicker);
1036         dest.writeBoolean(mIsVrOnly);
1037         dest.writeBoolean(mIsVirtualDeviceOnly);
1038         mService.writeToParcel(dest, flags);
1039         mSubtypes.writeToParcel(dest);
1040         dest.writeInt(mHandledConfigChanges);
1041         dest.writeBoolean(mSupportsStylusHandwriting);
1042         dest.writeBoolean(mSupportsConnectionlessStylusHandwriting);
1043         dest.writeString8(mStylusHandwritingSettingsActivityAttr);
1044     }
1045 
1046     /**
1047      * Used to make this class parcelable.
1048      */
1049     public static final @android.annotation.NonNull Parcelable.Creator<InputMethodInfo> CREATOR
1050             = new Parcelable.Creator<InputMethodInfo>() {
1051         @Override
1052         public InputMethodInfo createFromParcel(Parcel source) {
1053             return new InputMethodInfo(source);
1054         }
1055 
1056         @Override
1057         public InputMethodInfo[] newArray(int size) {
1058             return new InputMethodInfo[size];
1059         }
1060     };
1061 
1062     @Override
describeContents()1063     public int describeContents() {
1064         return 0;
1065     }
1066 }
1067