• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2010 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of 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,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.view.inputmethod;
18 
19 import android.annotation.AnyThread;
20 import android.annotation.FlaggedApi;
21 import android.annotation.NonNull;
22 import android.annotation.Nullable;
23 import android.annotation.StringRes;
24 import android.content.Context;
25 import android.content.pm.ApplicationInfo;
26 import android.content.res.Configuration;
27 import android.icu.text.DisplayContext;
28 import android.icu.text.LocaleDisplayNames;
29 import android.icu.util.ULocale;
30 import android.os.Parcel;
31 import android.os.Parcelable;
32 import android.text.TextUtils;
33 import android.util.Printer;
34 import android.util.Slog;
35 
36 import com.android.internal.inputmethod.SubtypeLocaleUtils;
37 
38 import java.util.ArrayList;
39 import java.util.Arrays;
40 import java.util.HashMap;
41 import java.util.HashSet;
42 import java.util.IllegalFormatException;
43 import java.util.List;
44 import java.util.Locale;
45 import java.util.Objects;
46 
47 /**
48  * This class is used to specify meta information of a subtype contained in an input method editor
49  * (IME). Subtype can describe locale (e.g. en_US, fr_FR...) and mode (e.g. voice, keyboard...),
50  * and is used for IME switch and settings. The input method subtype allows the system to bring up
51  * the specified subtype of the designated IME directly.
52  *
53  * <p>It should be defined in an XML resource file of the input method with the
54  * <code>&lt;subtype&gt;</code> element, which resides within an {@code <input-method>} element.
55  * For more information, see the guide to
56  * <a href="{@docRoot}guide/topics/text/creating-input-method.html">
57  * Creating an Input Method</a>.</p>
58  *
59  * @see InputMethodInfo
60  *
61  * @attr ref android.R.styleable#InputMethod_Subtype_label
62  * @attr ref android.R.styleable#InputMethod_Subtype_icon
63  * @attr ref android.R.styleable#InputMethod_Subtype_languageTag
64  * @attr ref android.R.styleable#InputMethod_Subtype_imeSubtypeLocale
65  * @attr ref android.R.styleable#InputMethod_Subtype_imeSubtypeMode
66  * @attr ref android.R.styleable#InputMethod_Subtype_imeSubtypeExtraValue
67  * @attr ref android.R.styleable#InputMethod_Subtype_isAuxiliary
68  * @attr ref android.R.styleable#InputMethod_Subtype_overridesImplicitlyEnabledSubtype
69  * @attr ref android.R.styleable#InputMethod_Subtype_subtypeId
70  * @attr ref android.R.styleable#InputMethod_Subtype_isAsciiCapable
71  */
72 public final class InputMethodSubtype implements Parcelable {
73     private static final String TAG = InputMethodSubtype.class.getSimpleName();
74     private static final String LANGUAGE_TAG_NONE = "";
75     private static final String EXTRA_VALUE_PAIR_SEPARATOR = ",";
76     private static final String EXTRA_VALUE_KEY_VALUE_SEPARATOR = "=";
77     // TODO: remove this
78     private static final String EXTRA_KEY_UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME =
79             "UntranslatableReplacementStringInSubtypeName";
80     /** {@hide} */
81     public static final int SUBTYPE_ID_NONE = 0;
82 
83     private static final String SUBTYPE_MODE_KEYBOARD = "keyboard";
84 
85     private static final String UNDEFINED_LANGUAGE_TAG = "und";
86 
87     private final boolean mIsAuxiliary;
88     private final boolean mOverridesImplicitlyEnabledSubtype;
89     private final boolean mIsAsciiCapable;
90     private final int mSubtypeHashCode;
91     private final int mSubtypeIconResId;
92     /** The subtype name resource identifier. */
93     private final int mSubtypeNameResId;
94     /** The untranslatable name of the subtype. */
95     @NonNull
96     private final CharSequence mSubtypeNameOverride;
97     /** The layout label string resource identifier. */
98     @StringRes
99     private final int mLayoutLabelResId;
100     /** The non-localized layout label. */
101     @NonNull
102     private final CharSequence mLayoutLabelNonLocalized;
103     private final String mPkLanguageTag;
104     private final String mPkLayoutType;
105     private final int mSubtypeId;
106     private final String mSubtypeLocale;
107     private final String mSubtypeLanguageTag;
108     private final String mSubtypeMode;
109     private final String mSubtypeExtraValue;
110     private final Object mLock = new Object();
111     private volatile Locale mCachedLocaleObj;
112     private volatile HashMap<String, String> mExtraValueHashMapCache;
113 
114     /**
115      * A volatile cache to optimize {@link #getCanonicalizedLanguageTag()}.
116      *
117      * <p>{@code null} means that the initial evaluation is not yet done.</p>
118      */
119     @Nullable
120     private volatile String mCachedCanonicalizedLanguageTag;
121 
122     /**
123      * InputMethodSubtypeBuilder is a builder class of InputMethodSubtype.
124      * This class is designed to be used with
125      * {@link android.view.inputmethod.InputMethodManager#setAdditionalInputMethodSubtypes}.
126      * The developer needs to be aware of what each parameter means.
127      */
128     public static class InputMethodSubtypeBuilder {
129         /**
130          * @param isAuxiliary should true when this subtype is auxiliary, false otherwise.
131          * An auxiliary subtype has the following differences with a regular subtype:
132          * - An auxiliary subtype cannot be chosen as the default IME in Settings.
133          * - The framework will never switch to this subtype through
134          *   {@link android.view.inputmethod.InputMethodManager#switchToLastInputMethod}.
135          * Note that the subtype will still be available in the IME switcher.
136          * The intent is to allow for IMEs to specify they are meant to be invoked temporarily
137          * in a one-shot way, and to return to the previous IME once finished (e.g. voice input).
138          */
setIsAuxiliary(boolean isAuxiliary)139         public InputMethodSubtypeBuilder setIsAuxiliary(boolean isAuxiliary) {
140             mIsAuxiliary = isAuxiliary;
141             return this;
142         }
143         private boolean mIsAuxiliary = false;
144 
145         /**
146          * @param overridesImplicitlyEnabledSubtype should be true if this subtype should be
147          * enabled by default if no other subtypes in the IME are enabled explicitly. Note that a
148          * subtype with this parameter set will not be shown in the list of subtypes in each IME's
149          * subtype enabler. A canonical use of this would be for an IME to supply an "automatic"
150          * subtype that adapts to the current system language.
151          */
setOverridesImplicitlyEnabledSubtype( boolean overridesImplicitlyEnabledSubtype)152         public InputMethodSubtypeBuilder setOverridesImplicitlyEnabledSubtype(
153                 boolean overridesImplicitlyEnabledSubtype) {
154             mOverridesImplicitlyEnabledSubtype = overridesImplicitlyEnabledSubtype;
155             return this;
156         }
157         private boolean mOverridesImplicitlyEnabledSubtype = false;
158 
159         /**
160          * @param isAsciiCapable should be true if this subtype is ASCII capable. If the subtype
161          * is ASCII capable, it should guarantee that the user can input ASCII characters with
162          * this subtype. This is important because many password fields only allow
163          * ASCII-characters.
164          */
setIsAsciiCapable(boolean isAsciiCapable)165         public InputMethodSubtypeBuilder setIsAsciiCapable(boolean isAsciiCapable) {
166             mIsAsciiCapable = isAsciiCapable;
167             return this;
168         }
169         private boolean mIsAsciiCapable = false;
170 
171         /**
172          * @param subtypeIconResId is a resource ID of the subtype icon drawable.
173          */
setSubtypeIconResId(int subtypeIconResId)174         public InputMethodSubtypeBuilder setSubtypeIconResId(int subtypeIconResId) {
175             mSubtypeIconResId = subtypeIconResId;
176             return this;
177         }
178         private int mSubtypeIconResId = 0;
179 
180         /**
181          * @param subtypeNameResId is the resource ID of the subtype name string.
182          * The string resource may have exactly one %s in it. If present,
183          * the %s part will be replaced with the locale's display name by
184          * the formatter. Please refer to {@link #getDisplayName} for details.
185          */
setSubtypeNameResId(int subtypeNameResId)186         public InputMethodSubtypeBuilder setSubtypeNameResId(int subtypeNameResId) {
187             mSubtypeNameResId = subtypeNameResId;
188             return this;
189         }
190         /** The subtype name resource identifier. */
191         private int mSubtypeNameResId = 0;
192 
193         /**
194          * Sets the untranslatable name of the subtype.
195          *
196          * This string is used as the subtype's display name if subtype's name res Id is 0.
197          *
198          * @param nameOverride is the name to set.
199          */
200         @NonNull
setSubtypeNameOverride( @onNull CharSequence nameOverride)201         public InputMethodSubtypeBuilder setSubtypeNameOverride(
202                 @NonNull CharSequence nameOverride) {
203             mSubtypeNameOverride = nameOverride;
204             return this;
205         }
206         /** The untranslatable name of the subtype. */
207         @NonNull
208         private CharSequence mSubtypeNameOverride = "";
209 
210         /**
211          * Sets the layout label string resource identifier.
212          *
213          * @param layoutLabelResId the layout label string resource identifier.
214          *
215          * @see #getLayoutDisplayName
216          */
217         @FlaggedApi(Flags.FLAG_IME_SWITCHER_REVAMP_API)
218         @NonNull
setLayoutLabelResource( @tringRes int layoutLabelResId)219         public InputMethodSubtypeBuilder setLayoutLabelResource(
220                 @StringRes int layoutLabelResId) {
221             if (!Flags.imeSwitcherRevampApi()) {
222                 return this;
223             }
224             mLayoutLabelResId = layoutLabelResId;
225             return this;
226         }
227         /** The layout label string resource identifier. */
228         @StringRes
229         private int mLayoutLabelResId = 0;
230 
231         /**
232          * Sets the non-localized layout label. This is used as the layout display name if the
233          * {@link #getLayoutLabelResource layoutLabelResource} is not set ({@code 0}).
234          *
235          * @param layoutLabelNonLocalized the non-localized layout label.
236          *
237          * @see #getLayoutDisplayName
238          */
239         @FlaggedApi(Flags.FLAG_IME_SWITCHER_REVAMP_API)
240         @NonNull
setLayoutLabelNonLocalized( @onNull CharSequence layoutLabelNonLocalized)241         public InputMethodSubtypeBuilder setLayoutLabelNonLocalized(
242                 @NonNull CharSequence layoutLabelNonLocalized) {
243             if (!Flags.imeSwitcherRevampApi()) {
244                 return this;
245             }
246             Objects.requireNonNull(layoutLabelNonLocalized,
247                     "layoutLabelNonLocalized cannot be null");
248             mLayoutLabelNonLocalized = layoutLabelNonLocalized;
249             return this;
250         }
251         /** The non-localized layout label. */
252         @NonNull
253         private CharSequence mLayoutLabelNonLocalized = "";
254 
255         /**
256          * Sets the physical keyboard hint information, such as language and layout.
257          *
258          * The system can use the hint information to automatically configure the physical keyboard
259          * for the subtype.
260          *
261          * @param languageTag is the preferred physical keyboard BCP-47 language tag. This is used
262          * to match the keyboardLocale attribute in the physical keyboard definition. If it's
263          * {@code null}, the subtype's language tag will be used.
264          * @param layoutType  is the preferred physical keyboard layout, which is used to match the
265          * keyboardLayoutType attribute in the physical keyboard definition. See
266          * {@link android.hardware.input.InputManager#ACTION_QUERY_KEYBOARD_LAYOUTS}.
267          */
268         @NonNull
setPhysicalKeyboardHint(@ullable ULocale languageTag, @NonNull String layoutType)269         public InputMethodSubtypeBuilder setPhysicalKeyboardHint(@Nullable ULocale languageTag,
270                 @NonNull String layoutType) {
271             Objects.requireNonNull(layoutType, "layoutType cannot be null");
272             mPkLanguageTag = languageTag == null ? "" : languageTag.toLanguageTag();
273             mPkLayoutType = layoutType;
274             return this;
275         }
276         private String mPkLanguageTag = "";
277         private String mPkLayoutType = "";
278 
279         /**
280          * @param subtypeId is the unique ID for this subtype. The input method framework keeps
281          * track of enabled subtypes by ID. When the IME package gets upgraded, enabled IDs will
282          * stay enabled even if other attributes are different. If the ID is unspecified or 0,
283          * Arrays.hashCode(new Object[] {locale, mode, extraValue,
284          * isAuxiliary, overridesImplicitlyEnabledSubtype, isAsciiCapable}) will be used instead.
285          */
setSubtypeId(int subtypeId)286         public InputMethodSubtypeBuilder setSubtypeId(int subtypeId) {
287             mSubtypeId = subtypeId;
288             return this;
289         }
290         private int mSubtypeId = SUBTYPE_ID_NONE;
291 
292         /**
293          * @param subtypeLocale is the locale supported by this subtype.
294          */
setSubtypeLocale(String subtypeLocale)295         public InputMethodSubtypeBuilder setSubtypeLocale(String subtypeLocale) {
296             mSubtypeLocale = subtypeLocale == null ? "" : subtypeLocale;
297             return this;
298         }
299         private String mSubtypeLocale = "";
300 
301         /**
302          * @param languageTag is the BCP-47 Language Tag supported by this subtype.
303          */
setLanguageTag(String languageTag)304         public InputMethodSubtypeBuilder setLanguageTag(String languageTag) {
305             mSubtypeLanguageTag = languageTag == null ? LANGUAGE_TAG_NONE : languageTag;
306             return this;
307         }
308         private String mSubtypeLanguageTag = LANGUAGE_TAG_NONE;
309 
310         /**
311          * @param subtypeMode is the mode supported by this subtype.
312          */
setSubtypeMode(String subtypeMode)313         public InputMethodSubtypeBuilder setSubtypeMode(String subtypeMode) {
314             mSubtypeMode = subtypeMode == null ? "" : subtypeMode;
315             return this;
316         }
317         private String mSubtypeMode = "";
318         /**
319          * @param subtypeExtraValue is the extra value of the subtype. This string is free-form,
320          * but the API supplies tools to deal with a key-value comma-separated list; see
321          * {@link #containsExtraValueKey} and {@link #getExtraValueOf}.
322          */
setSubtypeExtraValue(String subtypeExtraValue)323         public InputMethodSubtypeBuilder setSubtypeExtraValue(String subtypeExtraValue) {
324             mSubtypeExtraValue = subtypeExtraValue == null ? "" : subtypeExtraValue;
325             return this;
326         }
327         private String mSubtypeExtraValue = "";
328 
329         /**
330          * @return InputMethodSubtype using parameters in this InputMethodSubtypeBuilder.
331          */
build()332         public InputMethodSubtype build() {
333             return new InputMethodSubtype(this);
334         }
335     }
336 
getBuilder(int nameId, int iconId, String locale, String mode, String extraValue, boolean isAuxiliary, boolean overridesImplicitlyEnabledSubtype, int id, boolean isAsciiCapable)337     private static InputMethodSubtypeBuilder getBuilder(int nameId, int iconId,
338             String locale, String mode, String extraValue, boolean isAuxiliary,
339             boolean overridesImplicitlyEnabledSubtype, int id, boolean isAsciiCapable) {
340         final InputMethodSubtypeBuilder builder = new InputMethodSubtypeBuilder();
341         builder.mSubtypeNameResId = nameId;
342         builder.mSubtypeIconResId = iconId;
343         builder.mSubtypeLocale = locale;
344         builder.mSubtypeMode = mode;
345         builder.mSubtypeExtraValue = extraValue;
346         builder.mIsAuxiliary = isAuxiliary;
347         builder.mOverridesImplicitlyEnabledSubtype = overridesImplicitlyEnabledSubtype;
348         builder.mSubtypeId = id;
349         builder.mIsAsciiCapable = isAsciiCapable;
350         return builder;
351     }
352 
353     /**
354      * Constructor with no subtype ID specified.
355      * @deprecated use {@link InputMethodSubtypeBuilder} instead.
356      * Arguments for this constructor have the same meanings as
357      * {@link InputMethodSubtype#InputMethodSubtype(int, int, String, String, String, boolean,
358      * boolean, int)} except "id".
359      */
360     @Deprecated
InputMethodSubtype(int nameId, int iconId, String locale, String mode, String extraValue, boolean isAuxiliary, boolean overridesImplicitlyEnabledSubtype)361     public InputMethodSubtype(int nameId, int iconId, String locale, String mode, String extraValue,
362             boolean isAuxiliary, boolean overridesImplicitlyEnabledSubtype) {
363         this(nameId, iconId, locale, mode, extraValue, isAuxiliary,
364                 overridesImplicitlyEnabledSubtype, 0);
365     }
366 
367     /**
368      * Constructor.
369      * @deprecated use {@link InputMethodSubtypeBuilder} instead.
370      * "isAsciiCapable" is "false" in this constructor.
371      * @param nameId Resource ID of the subtype name string. The string resource may have exactly
372      * one %s in it. If there is, the %s part will be replaced with the locale's display name by
373      * the formatter. Please refer to {@link #getDisplayName} for details.
374      * @param iconId Resource ID of the subtype icon drawable.
375      * @param locale The locale supported by the subtype
376      * @param mode The mode supported by the subtype
377      * @param extraValue The extra value of the subtype. This string is free-form, but the API
378      * supplies tools to deal with a key-value comma-separated list; see
379      * {@link #containsExtraValueKey} and {@link #getExtraValueOf}.
380      * @param isAuxiliary true when this subtype is auxiliary, false otherwise. An auxiliary
381      * subtype will not be shown in the list of enabled IMEs for choosing the current IME in
382      * the Settings even when this subtype is enabled. Please note that this subtype will still
383      * be shown in the list of IMEs in the IME switcher to allow the user to tentatively switch
384      * to this subtype while an IME is shown. The framework will never switch the current IME to
385      * this subtype by {@link android.view.inputmethod.InputMethodManager#switchToLastInputMethod}.
386      * The intent of having this flag is to allow for IMEs that are invoked in a one-shot way as
387      * auxiliary input mode, and return to the previous IME once it is finished (e.g. voice input).
388      * @param overridesImplicitlyEnabledSubtype true when this subtype should be enabled by default
389      * if no other subtypes in the IME are enabled explicitly. Note that a subtype with this
390      * parameter being true will not be shown in the list of subtypes in each IME's subtype enabler.
391      * Having an "automatic" subtype is an example use of this flag.
392      * @param id The unique ID for the subtype. The input method framework keeps track of enabled
393      * subtypes by ID. When the IME package gets upgraded, enabled IDs will stay enabled even if
394      * other attributes are different. If the ID is unspecified or 0,
395      * Arrays.hashCode(new Object[] {locale, mode, extraValue,
396      * isAuxiliary, overridesImplicitlyEnabledSubtype, isAsciiCapable}) will be used instead.
397      */
398     @Deprecated
InputMethodSubtype(int nameId, int iconId, String locale, String mode, String extraValue, boolean isAuxiliary, boolean overridesImplicitlyEnabledSubtype, int id)399     public InputMethodSubtype(int nameId, int iconId, String locale, String mode, String extraValue,
400             boolean isAuxiliary, boolean overridesImplicitlyEnabledSubtype, int id) {
401         this(getBuilder(nameId, iconId, locale, mode, extraValue, isAuxiliary,
402                 overridesImplicitlyEnabledSubtype, id, false));
403     }
404 
405     /**
406      * Constructor.
407      * @param builder Builder for InputMethodSubtype
408      */
InputMethodSubtype(InputMethodSubtypeBuilder builder)409     private InputMethodSubtype(InputMethodSubtypeBuilder builder) {
410         mSubtypeNameResId = builder.mSubtypeNameResId;
411         mSubtypeNameOverride = builder.mSubtypeNameOverride;
412         mLayoutLabelResId = builder.mLayoutLabelResId;
413         mLayoutLabelNonLocalized = builder.mLayoutLabelNonLocalized;
414         mPkLanguageTag = builder.mPkLanguageTag;
415         mPkLayoutType = builder.mPkLayoutType;
416         mSubtypeIconResId = builder.mSubtypeIconResId;
417         mSubtypeLocale = builder.mSubtypeLocale;
418         mSubtypeLanguageTag = builder.mSubtypeLanguageTag;
419         mSubtypeMode = builder.mSubtypeMode;
420         mSubtypeExtraValue = builder.mSubtypeExtraValue;
421         mIsAuxiliary = builder.mIsAuxiliary;
422         mOverridesImplicitlyEnabledSubtype = builder.mOverridesImplicitlyEnabledSubtype;
423         mSubtypeId = builder.mSubtypeId;
424         mIsAsciiCapable = builder.mIsAsciiCapable;
425         // If hashCode() of this subtype is 0 and you want to specify it as an id of this subtype,
426         // just specify 0 as this subtype's id. Then, this subtype's id is treated as 0.
427         if (mSubtypeId != SUBTYPE_ID_NONE) {
428             mSubtypeHashCode = mSubtypeId;
429         } else {
430             mSubtypeHashCode = hashCodeInternal(mSubtypeLocale, mSubtypeMode, mSubtypeExtraValue,
431                     mIsAuxiliary, mOverridesImplicitlyEnabledSubtype, mIsAsciiCapable);
432         }
433     }
434 
InputMethodSubtype(Parcel source)435     InputMethodSubtype(Parcel source) {
436         String s;
437         mSubtypeNameResId = source.readInt();
438         CharSequence cs = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
439         mSubtypeNameOverride = cs != null ? cs : "";
440         mLayoutLabelResId = source.readInt();
441         cs = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
442         mLayoutLabelNonLocalized = cs != null ? cs : "";
443         s = source.readString8();
444         mPkLanguageTag = s != null ? s : "";
445         s = source.readString8();
446         mPkLayoutType = s != null ? s : "";
447         mSubtypeIconResId = source.readInt();
448         s = source.readString();
449         mSubtypeLocale = s != null ? s : "";
450         s = source.readString();
451         mSubtypeLanguageTag = s != null ? s : LANGUAGE_TAG_NONE;
452         s = source.readString();
453         mSubtypeMode = s != null ? s : "";
454         s = source.readString();
455         mSubtypeExtraValue = s != null ? s : "";
456         mIsAuxiliary = (source.readInt() == 1);
457         mOverridesImplicitlyEnabledSubtype = (source.readInt() == 1);
458         mSubtypeHashCode = source.readInt();
459         mSubtypeId = source.readInt();
460         mIsAsciiCapable = (source.readInt() == 1);
461     }
462 
463     /**
464      * @return Resource ID of the subtype name string.
465      */
getNameResId()466     public int getNameResId() {
467         return mSubtypeNameResId;
468     }
469 
470     /**
471      * @return The subtype's untranslatable name string.
472      */
473     @NonNull
getNameOverride()474     public CharSequence getNameOverride() {
475         return mSubtypeNameOverride;
476     }
477 
478     /**
479      * Returns the layout label string resource identifier.
480      */
481     @FlaggedApi(Flags.FLAG_IME_SWITCHER_REVAMP_API)
482     @StringRes
getLayoutLabelResource()483     public int getLayoutLabelResource() {
484         return mLayoutLabelResId;
485     }
486 
487     /**
488      * Returns the non-localized layout label.
489      */
490     @FlaggedApi(Flags.FLAG_IME_SWITCHER_REVAMP_API)
491     @NonNull
getLayoutLabelNonLocalized()492     public CharSequence getLayoutLabelNonLocalized() {
493         return mLayoutLabelNonLocalized;
494     }
495 
496     /**
497      * Returns the physical keyboard BCP-47 language tag.
498      *
499      * @attr ref android.R.styleable#InputMethod_Subtype_physicalKeyboardHintLanguageTag
500      * @see InputMethodSubtypeBuilder#setPhysicalKeyboardHint
501      */
502     @Nullable
getPhysicalKeyboardHintLanguageTag()503     public ULocale getPhysicalKeyboardHintLanguageTag() {
504         return TextUtils.isEmpty(mPkLanguageTag) ? null : ULocale.forLanguageTag(mPkLanguageTag);
505     }
506 
507     /**
508      * Returns the physical keyboard layout type string.
509      *
510      * @attr ref android.R.styleable#InputMethod_Subtype_physicalKeyboardHintLayoutType
511      * @see InputMethodSubtypeBuilder#setPhysicalKeyboardHint
512      */
513     @NonNull
getPhysicalKeyboardHintLayoutType()514     public String getPhysicalKeyboardHintLayoutType() {
515         return mPkLayoutType;
516     }
517 
518     /**
519      * @return Resource ID of the subtype icon drawable.
520      */
getIconResId()521     public int getIconResId() {
522         return mSubtypeIconResId;
523     }
524 
525     /**
526      * @return The locale of the subtype. This method returns the "locale" string parameter passed
527      * to the constructor.
528      *
529      * @deprecated Use {@link #getLanguageTag()} instead.
530      */
531     @Deprecated
532     @NonNull
getLocale()533     public String getLocale() {
534         return mSubtypeLocale;
535     }
536 
537     /**
538      * @return the BCP-47 Language Tag of the subtype.  Returns an empty string when no Language Tag
539      * is specified.
540      *
541      * @see Locale#forLanguageTag(String)
542      */
543     @NonNull
getLanguageTag()544     public String getLanguageTag() {
545         return mSubtypeLanguageTag;
546     }
547 
548     /**
549      * @return {@link Locale} constructed from {@link #getLanguageTag()}. If the Language Tag is not
550      * specified, then try to construct from {@link #getLocale()}
551      *
552      * <p>TODO: Consider to make this a public API, or move this to support lib.</p>
553      * @hide
554      */
555     @Nullable
getLocaleObject()556     public Locale getLocaleObject() {
557         if (mCachedLocaleObj != null) {
558             return mCachedLocaleObj;
559         }
560         synchronized (mLock) {
561             if (mCachedLocaleObj != null) {
562                 return mCachedLocaleObj;
563             }
564             if (!TextUtils.isEmpty(mSubtypeLanguageTag)) {
565                 mCachedLocaleObj = Locale.forLanguageTag(mSubtypeLanguageTag);
566             } else {
567                 mCachedLocaleObj = SubtypeLocaleUtils.constructLocaleFromString(mSubtypeLocale);
568             }
569             return mCachedLocaleObj;
570         }
571     }
572 
573     /**
574      * Returns a canonicalized BCP 47 Language Tag initialized with {@link #getLocaleObject()}.
575      *
576      * <p>This has an internal cache mechanism.  Subsequent calls are in general cheap and fast.</p>
577      *
578      * @return a canonicalized BCP 47 Language Tag initialized with {@link #getLocaleObject()}. An
579      *         empty string if {@link #getLocaleObject()} returns {@code null} or an empty
580      *         {@link Locale} object.
581      * @hide
582      */
583     @AnyThread
584     @NonNull
getCanonicalizedLanguageTag()585     public String getCanonicalizedLanguageTag() {
586         final String cachedValue = mCachedCanonicalizedLanguageTag;
587         if (cachedValue != null) {
588             return cachedValue;
589         }
590 
591         String result = null;
592         final Locale locale = getLocaleObject();
593         if (locale != null) {
594             final String langTag = locale.toLanguageTag();
595             if (!TextUtils.isEmpty(langTag)) {
596                 result = ULocale.createCanonical(ULocale.forLanguageTag(langTag)).toLanguageTag();
597             }
598         }
599         result = TextUtils.emptyIfNull(result);
600         mCachedCanonicalizedLanguageTag = result;
601         return result;
602     }
603 
604     /**
605      * Determines whether this {@link InputMethodSubtype} can be used as the key of mapping rules
606      * between {@link InputMethodSubtype} and hardware keyboard layout.
607      *
608      * <p>Note that in a future build may require different rules.  Design the system so that the
609      * system can automatically take care of any rule changes upon OTAs.</p>
610      *
611      * @return {@code true} if this {@link InputMethodSubtype} can be used as the key of mapping
612      *         rules between {@link InputMethodSubtype} and hardware keyboard layout.
613      * @hide
614      */
isSuitableForPhysicalKeyboardLayoutMapping()615     public boolean isSuitableForPhysicalKeyboardLayoutMapping() {
616         if (hashCode() == SUBTYPE_ID_NONE) {
617             return false;
618         }
619         if (!TextUtils.equals(getMode(), SUBTYPE_MODE_KEYBOARD)) {
620             return false;
621         }
622         return !isAuxiliary();
623     }
624 
625     /**
626      * @return The mode of the subtype.
627      */
getMode()628     public String getMode() {
629         return mSubtypeMode;
630     }
631 
632     /**
633      * @return The extra value of the subtype.
634      */
getExtraValue()635     public String getExtraValue() {
636         return mSubtypeExtraValue;
637     }
638 
639     /**
640      * @return true if this subtype is auxiliary, false otherwise. An auxiliary subtype will not be
641      * shown in the list of enabled IMEs for choosing the current IME in the Settings even when this
642      * subtype is enabled. Please note that this subtype will still be shown in the list of IMEs in
643      * the IME switcher to allow the user to tentatively switch to this subtype while an IME is
644      * shown. The framework will never switch the current IME to this subtype by
645      * {@link android.view.inputmethod.InputMethodManager#switchToLastInputMethod}.
646      * The intent of having this flag is to allow for IMEs that are invoked in a one-shot way as
647      * auxiliary input mode, and return to the previous IME once it is finished (e.g. voice input).
648      */
isAuxiliary()649     public boolean isAuxiliary() {
650         return mIsAuxiliary;
651     }
652 
653     /**
654      * @return true when this subtype will be enabled by default if no other subtypes in the IME
655      * are enabled explicitly, false otherwise. Note that a subtype with this method returning true
656      * will not be shown in the list of subtypes in each IME's subtype enabler. Having an
657      * "automatic" subtype is an example use of this flag.
658      */
overridesImplicitlyEnabledSubtype()659     public boolean overridesImplicitlyEnabledSubtype() {
660         return mOverridesImplicitlyEnabledSubtype;
661     }
662 
663     /**
664      * @return true if this subtype is Ascii capable, false otherwise. If the subtype is ASCII
665      * capable, it should guarantee that the user can input ASCII characters with this subtype.
666      * This is important because many password fields only allow ASCII-characters.
667      */
isAsciiCapable()668     public boolean isAsciiCapable() {
669         return mIsAsciiCapable;
670     }
671 
672     /**
673      * Returns a display name for this subtype.
674      *
675      * <p>If {@code subtypeNameResId} is specified (!= 0) text generated from that resource will
676      * be returned. The localized string resource of the label should be capitalized for inclusion
677      * in UI lists. The string resource may contain at most one {@code %s}. If present, the
678      * {@code %s} will be replaced with the display name of the subtype locale in the user's locale.
679      *
680      * <p>If {@code subtypeNameResId} is not specified (== 0) the framework returns the display name
681      * of the subtype locale, as capitalized for use in UI lists, in the user's locale.
682      *
683      * @param context {@link Context} will be used for getting {@link Locale} and
684      * {@link android.content.pm.PackageManager}.
685      * @param packageName The package name of the input method.
686      * @param appInfo The {@link ApplicationInfo} of the input method.
687      * @return a display name for this subtype.
688      */
689     @NonNull
getDisplayName( Context context, String packageName, ApplicationInfo appInfo)690     public CharSequence getDisplayName(
691             Context context, String packageName, ApplicationInfo appInfo) {
692         if (mSubtypeNameResId == 0) {
693             return TextUtils.isEmpty(mSubtypeNameOverride)
694                     ? getLocaleDisplayName(
695                             getLocaleFromContext(context), getLocaleObject(),
696                             DisplayContext.CAPITALIZATION_FOR_UI_LIST_OR_MENU)
697                     : mSubtypeNameOverride;
698         }
699 
700         final CharSequence subtypeName = context.getPackageManager().getText(
701                 packageName, mSubtypeNameResId, appInfo);
702         if (TextUtils.isEmpty(subtypeName)) {
703             return "";
704         }
705         final String subtypeNameString = subtypeName.toString();
706         String replacementString;
707         if (containsExtraValueKey(EXTRA_KEY_UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME)) {
708             replacementString = getExtraValueOf(
709                     EXTRA_KEY_UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME);
710         } else {
711             final DisplayContext displayContext;
712             if (TextUtils.equals(subtypeNameString, "%s")) {
713                 displayContext = DisplayContext.CAPITALIZATION_FOR_UI_LIST_OR_MENU;
714             } else if (subtypeNameString.startsWith("%s")) {
715                 displayContext = DisplayContext.CAPITALIZATION_FOR_BEGINNING_OF_SENTENCE;
716             } else {
717                 displayContext = DisplayContext.CAPITALIZATION_FOR_MIDDLE_OF_SENTENCE;
718             }
719             replacementString = getLocaleDisplayName(getLocaleFromContext(context),
720                     getLocaleObject(), displayContext);
721         }
722         if (replacementString == null) {
723             replacementString = "";
724         }
725         try {
726             return String.format(subtypeNameString, replacementString);
727         } catch (IllegalFormatException e) {
728             Slog.w(TAG, "Found illegal format in subtype name(" + subtypeName + "): " + e);
729             return "";
730         }
731     }
732 
733     /**
734      * Returns the layout display name.
735      *
736      * <p>If {@code layoutLabelResource} is non-zero (specified through
737      * {@link InputMethodSubtypeBuilder#setLayoutLabelResource setLayoutLabelResource}), the
738      * text generated from that resource will be returned. The localized string resource of the
739      * label should be capitalized for inclusion in UI lists.
740      *
741      * <p>If {@code layoutLabelResource} is zero, the framework returns the non-localized
742      * layout label, if specified through
743      * {@link InputMethodSubtypeBuilder#setLayoutLabelNonLocalized setLayoutLabelNonLocalized}.
744      *
745      * @param context The context used for getting the
746      * {@link android.content.pm.PackageManager PackageManager}.
747      * @param imeAppInfo The {@link ApplicationInfo} of the input method.
748      * @return the layout display name.
749      */
750     @NonNull
751     @FlaggedApi(Flags.FLAG_IME_SWITCHER_REVAMP_API)
getLayoutDisplayName(@onNull Context context, @NonNull ApplicationInfo imeAppInfo)752     public CharSequence getLayoutDisplayName(@NonNull Context context,
753             @NonNull ApplicationInfo imeAppInfo) {
754         if (!Flags.imeSwitcherRevampApi()) {
755             return "";
756         }
757         Objects.requireNonNull(context, "context cannot be null");
758         Objects.requireNonNull(imeAppInfo, "imeAppInfo cannot be null");
759         if (mLayoutLabelResId == 0) {
760             return mLayoutLabelNonLocalized;
761         }
762 
763         final CharSequence subtypeLayoutName = context.getPackageManager().getText(
764                 imeAppInfo.packageName, mLayoutLabelResId, imeAppInfo);
765         if (TextUtils.isEmpty(subtypeLayoutName)) {
766             return "";
767         }
768         return subtypeLayoutName;
769     }
770 
771     @Nullable
getLocaleFromContext(@ullable final Context context)772     private static Locale getLocaleFromContext(@Nullable final Context context) {
773         if (context == null) {
774             return null;
775         }
776         if (context.getResources() == null) {
777             return null;
778         }
779         final Configuration configuration = context.getResources().getConfiguration();
780         if (configuration == null) {
781             return null;
782         }
783         return configuration.getLocales().get(0);
784     }
785 
786     /**
787      * @param displayLocale {@link Locale} to be used to display {@code localeToDisplay}
788      * @param localeToDisplay {@link Locale} to be displayed in {@code displayLocale}
789      * @param displayContext context parameter to be used to display {@code localeToDisplay} in
790      * {@code displayLocale}
791      * @return Returns the name of the {@code localeToDisplay} in the user's current locale.
792      */
793     @NonNull
getLocaleDisplayName( @ullable Locale displayLocale, @Nullable Locale localeToDisplay, final DisplayContext displayContext)794     private static String getLocaleDisplayName(
795             @Nullable Locale displayLocale, @Nullable Locale localeToDisplay,
796             final DisplayContext displayContext) {
797         if (localeToDisplay == null) {
798             return "";
799         }
800         final Locale nonNullDisplayLocale =
801                 displayLocale != null ? displayLocale : Locale.getDefault();
802         return LocaleDisplayNames
803                 .getInstance(nonNullDisplayLocale, displayContext)
804                 .localeDisplayName(localeToDisplay);
805     }
806 
getExtraValueHashMap()807     private HashMap<String, String> getExtraValueHashMap() {
808         synchronized (this) {
809             HashMap<String, String> extraValueMap = mExtraValueHashMapCache;
810             if (extraValueMap != null) {
811                 return extraValueMap;
812             }
813             extraValueMap = new HashMap<>();
814             final String[] pairs = mSubtypeExtraValue.split(EXTRA_VALUE_PAIR_SEPARATOR);
815             for (int i = 0; i < pairs.length; ++i) {
816                 final String[] pair = pairs[i].split(EXTRA_VALUE_KEY_VALUE_SEPARATOR);
817                 if (pair.length == 1) {
818                     extraValueMap.put(pair[0], null);
819                 } else if (pair.length > 1) {
820                     if (pair.length > 2) {
821                         Slog.w(TAG, "ExtraValue has two or more '='s");
822                     }
823                     extraValueMap.put(pair[0], pair[1]);
824                 }
825             }
826             mExtraValueHashMapCache = extraValueMap;
827             return extraValueMap;
828         }
829     }
830 
831     /**
832      * The string of ExtraValue in subtype should be defined as follows:
833      * example: key0,key1=value1,key2,key3,key4=value4
834      * @param key The key of extra value
835      * @return The subtype contains specified the extra value
836      */
containsExtraValueKey(String key)837     public boolean containsExtraValueKey(String key) {
838         return getExtraValueHashMap().containsKey(key);
839     }
840 
841     /**
842      * The string of ExtraValue in subtype should be defined as follows:
843      * example: key0,key1=value1,key2,key3,key4=value4
844      * @param key The key of extra value
845      * @return The value of the specified key
846      */
getExtraValueOf(String key)847     public String getExtraValueOf(String key) {
848         return getExtraValueHashMap().get(key);
849     }
850 
851     @Override
hashCode()852     public int hashCode() {
853         return mSubtypeHashCode;
854     }
855 
856     /**
857      * @hide
858      * @return {@code true} if a valid subtype ID exists.
859      */
hasSubtypeId()860     public final boolean hasSubtypeId() {
861         return mSubtypeId != SUBTYPE_ID_NONE;
862     }
863 
864     /**
865      * @hide
866      * @return subtype ID. {@code 0} means that not subtype ID is specified.
867      */
getSubtypeId()868     public final int getSubtypeId() {
869         return mSubtypeId;
870     }
871 
872     @Override
equals(@ullable Object o)873     public boolean equals(@Nullable Object o) {
874         if (o instanceof InputMethodSubtype) {
875             InputMethodSubtype subtype = (InputMethodSubtype) o;
876             if (subtype.mSubtypeId != 0 || mSubtypeId != 0) {
877                 return (subtype.hashCode() == hashCode());
878             }
879             return (subtype.hashCode() == hashCode())
880                     && (subtype.getLocale().equals(getLocale()))
881                     && (subtype.getLanguageTag().equals(getLanguageTag()))
882                     && (subtype.getMode().equals(getMode()))
883                     && (subtype.getExtraValue().equals(getExtraValue()))
884                     && (subtype.isAuxiliary() == isAuxiliary())
885                     && (subtype.overridesImplicitlyEnabledSubtype()
886                             == overridesImplicitlyEnabledSubtype())
887                     && (subtype.isAsciiCapable() == isAsciiCapable());
888         }
889         return false;
890     }
891 
892     @Override
describeContents()893     public int describeContents() {
894         return 0;
895     }
896 
897     @Override
writeToParcel(Parcel dest, int parcelableFlags)898     public void writeToParcel(Parcel dest, int parcelableFlags) {
899         dest.writeInt(mSubtypeNameResId);
900         TextUtils.writeToParcel(mSubtypeNameOverride, dest, parcelableFlags);
901         dest.writeInt(mLayoutLabelResId);
902         TextUtils.writeToParcel(mLayoutLabelNonLocalized, dest, parcelableFlags);
903         dest.writeString8(mPkLanguageTag);
904         dest.writeString8(mPkLayoutType);
905         dest.writeInt(mSubtypeIconResId);
906         dest.writeString(mSubtypeLocale);
907         dest.writeString(mSubtypeLanguageTag);
908         dest.writeString(mSubtypeMode);
909         dest.writeString(mSubtypeExtraValue);
910         dest.writeInt(mIsAuxiliary ? 1 : 0);
911         dest.writeInt(mOverridesImplicitlyEnabledSubtype ? 1 : 0);
912         dest.writeInt(mSubtypeHashCode);
913         dest.writeInt(mSubtypeId);
914         dest.writeInt(mIsAsciiCapable ? 1 : 0);
915     }
916 
dump(@onNull Printer pw, @NonNull String prefix)917     void dump(@NonNull Printer pw, @NonNull String prefix) {
918         pw.println(prefix + "mSubtypeNameOverride=" + mSubtypeNameOverride
919                 + " mLayoutLabelNonLocalized=" + mLayoutLabelNonLocalized
920                 + " mPkLanguageTag=" + mPkLanguageTag
921                 + " mPkLayoutType=" + mPkLayoutType
922                 + " mSubtypeId=" + mSubtypeId
923                 + " mSubtypeLocale=" + mSubtypeLocale
924                 + " mSubtypeLanguageTag=" + mSubtypeLanguageTag
925                 + " mSubtypeMode=" + mSubtypeMode
926                 + " mIsAuxiliary=" + mIsAuxiliary
927                 + " mOverridesImplicitlyEnabledSubtype=" + mOverridesImplicitlyEnabledSubtype
928                 + " mIsAsciiCapable=" + mIsAsciiCapable
929                 + " mSubtypeHashCode=" + mSubtypeHashCode);
930     }
931 
932     public static final @android.annotation.NonNull Parcelable.Creator<InputMethodSubtype> CREATOR
933             = new Parcelable.Creator<InputMethodSubtype>() {
934         @Override
935         public InputMethodSubtype createFromParcel(Parcel source) {
936             return new InputMethodSubtype(source);
937         }
938 
939         @Override
940         public InputMethodSubtype[] newArray(int size) {
941             return new InputMethodSubtype[size];
942         }
943     };
944 
hashCodeInternal(String locale, String mode, String extraValue, boolean isAuxiliary, boolean overridesImplicitlyEnabledSubtype, boolean isAsciiCapable)945     private static int hashCodeInternal(String locale, String mode, String extraValue,
946             boolean isAuxiliary, boolean overridesImplicitlyEnabledSubtype,
947             boolean isAsciiCapable) {
948         // CAVEAT: Must revisit how to compute needsToCalculateCompatibleHashCode when a new
949         // attribute is added in order to avoid enabled subtypes being unexpectedly disabled.
950         final boolean needsToCalculateCompatibleHashCode = !isAsciiCapable;
951         if (needsToCalculateCompatibleHashCode) {
952             return Arrays.hashCode(new Object[] {locale, mode, extraValue, isAuxiliary,
953                     overridesImplicitlyEnabledSubtype});
954         }
955         return Arrays.hashCode(new Object[] {locale, mode, extraValue, isAuxiliary,
956                 overridesImplicitlyEnabledSubtype, isAsciiCapable});
957     }
958 
959     /**
960      * Sort the list of InputMethodSubtype
961      * @param imi InputMethodInfo of which subtypes are subject to be sorted
962      * @param subtypeList List of InputMethodSubtype which will be sorted
963      * @return Sorted list of subtypes
964      * @hide
965      */
sort(InputMethodInfo imi, List<InputMethodSubtype> subtypeList)966     public static List<InputMethodSubtype> sort(InputMethodInfo imi,
967             List<InputMethodSubtype> subtypeList) {
968         if (imi == null) return subtypeList;
969         final HashSet<InputMethodSubtype> inputSubtypesSet = new HashSet<InputMethodSubtype>(
970                 subtypeList);
971         final ArrayList<InputMethodSubtype> sortedList = new ArrayList<InputMethodSubtype>();
972         int N = imi.getSubtypeCount();
973         for (int i = 0; i < N; ++i) {
974             InputMethodSubtype subtype = imi.getSubtypeAt(i);
975             if (inputSubtypesSet.contains(subtype)) {
976                 sortedList.add(subtype);
977                 inputSubtypesSet.remove(subtype);
978             }
979         }
980         // If subtypes in inputSubtypesSet remain, that means these subtypes are not
981         // contained in imi, so the remaining subtypes will be appended.
982         for (InputMethodSubtype subtype: inputSubtypesSet) {
983             sortedList.add(subtype);
984         }
985         return sortedList;
986     }
987 }
988