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