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