summaryrefslogtreecommitdiff
path: root/android/view/inputmethod/InputMethodSubtype.java
blob: a7d7a8d67f64679707fe18b028eb8d7452f6a239 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
/*
 * Copyright (C) 2010 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.view.inputmethod;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.res.Configuration;
import android.icu.text.DisplayContext;
import android.icu.text.LocaleDisplayNames;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
import android.util.Slog;

import com.android.internal.inputmethod.InputMethodUtils;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IllegalFormatException;
import java.util.List;
import java.util.Locale;

/**
 * This class is used to specify meta information of a subtype contained in an input method editor
 * (IME). Subtype can describe locale (e.g. en_US, fr_FR...) and mode (e.g. voice, keyboard...),
 * and is used for IME switch and settings. The input method subtype allows the system to bring up
 * the specified subtype of the designated IME directly.
 *
 * <p>It should be defined in an XML resource file of the input method with the
 * <code>&lt;subtype&gt;</code> element, which resides within an {@code <input-method>} element.
 * For more information, see the guide to
 * <a href="{@docRoot}guide/topics/text/creating-input-method.html">
 * Creating an Input Method</a>.</p>
 *
 * @see InputMethodInfo
 *
 * @attr ref android.R.styleable#InputMethod_Subtype_label
 * @attr ref android.R.styleable#InputMethod_Subtype_icon
 * @attr ref android.R.styleable#InputMethod_Subtype_languageTag
 * @attr ref android.R.styleable#InputMethod_Subtype_imeSubtypeLocale
 * @attr ref android.R.styleable#InputMethod_Subtype_imeSubtypeMode
 * @attr ref android.R.styleable#InputMethod_Subtype_imeSubtypeExtraValue
 * @attr ref android.R.styleable#InputMethod_Subtype_isAuxiliary
 * @attr ref android.R.styleable#InputMethod_Subtype_overridesImplicitlyEnabledSubtype
 * @attr ref android.R.styleable#InputMethod_Subtype_subtypeId
 * @attr ref android.R.styleable#InputMethod_Subtype_isAsciiCapable
 */
public final class InputMethodSubtype implements Parcelable {
    private static final String TAG = InputMethodSubtype.class.getSimpleName();
    private static final String LANGUAGE_TAG_NONE = "";
    private static final String EXTRA_VALUE_PAIR_SEPARATOR = ",";
    private static final String EXTRA_VALUE_KEY_VALUE_SEPARATOR = "=";
    // TODO: remove this
    private static final String EXTRA_KEY_UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME =
            "UntranslatableReplacementStringInSubtypeName";
    private static final int SUBTYPE_ID_NONE = 0;

    private final boolean mIsAuxiliary;
    private final boolean mOverridesImplicitlyEnabledSubtype;
    private final boolean mIsAsciiCapable;
    private final int mSubtypeHashCode;
    private final int mSubtypeIconResId;
    private final int mSubtypeNameResId;
    private final int mSubtypeId;
    private final String mSubtypeLocale;
    private final String mSubtypeLanguageTag;
    private final String mSubtypeMode;
    private final String mSubtypeExtraValue;
    private final Object mLock = new Object();
    private volatile Locale mCachedLocaleObj;
    private volatile HashMap<String, String> mExtraValueHashMapCache;

    /**
     * InputMethodSubtypeBuilder is a builder class of InputMethodSubtype.
     * This class is designed to be used with
     * {@link android.view.inputmethod.InputMethodManager#setAdditionalInputMethodSubtypes}.
     * The developer needs to be aware of what each parameter means.
     */
    public static class InputMethodSubtypeBuilder {
        /**
         * @param isAuxiliary should true when this subtype is auxiliary, false otherwise.
         * An auxiliary subtype has the following differences with a regular subtype:
         * - An auxiliary subtype cannot be chosen as the default IME in Settings.
         * - The framework will never switch to this subtype through
         *   {@link android.view.inputmethod.InputMethodManager#switchToLastInputMethod}.
         * Note that the subtype will still be available in the IME switcher.
         * The intent is to allow for IMEs to specify they are meant to be invoked temporarily
         * in a one-shot way, and to return to the previous IME once finished (e.g. voice input).
         */
        public InputMethodSubtypeBuilder setIsAuxiliary(boolean isAuxiliary) {
            mIsAuxiliary = isAuxiliary;
            return this;
        }
        private boolean mIsAuxiliary = false;

        /**
         * @param overridesImplicitlyEnabledSubtype should be true if this subtype should be
         * enabled by default if no other subtypes in the IME are enabled explicitly. Note that a
         * subtype with this parameter set will not be shown in the list of subtypes in each IME's
         * subtype enabler. A canonical use of this would be for an IME to supply an "automatic"
         * subtype that adapts to the current system language.
         */
        public InputMethodSubtypeBuilder setOverridesImplicitlyEnabledSubtype(
                boolean overridesImplicitlyEnabledSubtype) {
            mOverridesImplicitlyEnabledSubtype = overridesImplicitlyEnabledSubtype;
            return this;
        }
        private boolean mOverridesImplicitlyEnabledSubtype = false;

        /**
         * @param isAsciiCapable should be true if this subtype is ASCII capable. If the subtype
         * is ASCII capable, it should guarantee that the user can input ASCII characters with
         * this subtype. This is important because many password fields only allow
         * ASCII-characters.
         */
        public InputMethodSubtypeBuilder setIsAsciiCapable(boolean isAsciiCapable) {
            mIsAsciiCapable = isAsciiCapable;
            return this;
        }
        private boolean mIsAsciiCapable = false;

        /**
         * @param subtypeIconResId is a resource ID of the subtype icon drawable.
         */
        public InputMethodSubtypeBuilder setSubtypeIconResId(int subtypeIconResId) {
            mSubtypeIconResId = subtypeIconResId;
            return this;
        }
        private int mSubtypeIconResId = 0;

        /**
         * @param subtypeNameResId is the resource ID of the subtype name string.
         * The string resource may have exactly one %s in it. If present,
         * the %s part will be replaced with the locale's display name by
         * the formatter. Please refer to {@link #getDisplayName} for details.
         */
        public InputMethodSubtypeBuilder setSubtypeNameResId(int subtypeNameResId) {
            mSubtypeNameResId = subtypeNameResId;
            return this;
        }
        private int mSubtypeNameResId = 0;

        /**
         * @param subtypeId is the unique ID for this subtype. The input method framework keeps
         * track of enabled subtypes by ID. When the IME package gets upgraded, enabled IDs will
         * stay enabled even if other attributes are different. If the ID is unspecified or 0,
         * Arrays.hashCode(new Object[] {locale, mode, extraValue,
         * isAuxiliary, overridesImplicitlyEnabledSubtype, isAsciiCapable}) will be used instead.
         */
        public InputMethodSubtypeBuilder setSubtypeId(int subtypeId) {
            mSubtypeId = subtypeId;
            return this;
        }
        private int mSubtypeId = SUBTYPE_ID_NONE;

        /**
         * @param subtypeLocale is the locale supported by this subtype.
         */
        public InputMethodSubtypeBuilder setSubtypeLocale(String subtypeLocale) {
            mSubtypeLocale = subtypeLocale == null ? "" : subtypeLocale;
            return this;
        }
        private String mSubtypeLocale = "";

        /**
         * @param languageTag is the BCP-47 Language Tag supported by this subtype.
         */
        public InputMethodSubtypeBuilder setLanguageTag(String languageTag) {
            mSubtypeLanguageTag = languageTag == null ? LANGUAGE_TAG_NONE : languageTag;
            return this;
        }
        private String mSubtypeLanguageTag = LANGUAGE_TAG_NONE;

        /**
         * @param subtypeMode is the mode supported by this subtype.
         */
        public InputMethodSubtypeBuilder setSubtypeMode(String subtypeMode) {
            mSubtypeMode = subtypeMode == null ? "" : subtypeMode;
            return this;
        }
        private String mSubtypeMode = "";
        /**
         * @param subtypeExtraValue is the extra value of the subtype. This string is free-form,
         * but the API supplies tools to deal with a key-value comma-separated list; see
         * {@link #containsExtraValueKey} and {@link #getExtraValueOf}.
         */
        public InputMethodSubtypeBuilder setSubtypeExtraValue(String subtypeExtraValue) {
            mSubtypeExtraValue = subtypeExtraValue == null ? "" : subtypeExtraValue;
            return this;
        }
        private String mSubtypeExtraValue = "";

        /**
         * @return InputMethodSubtype using parameters in this InputMethodSubtypeBuilder.
         */
        public InputMethodSubtype build() {
            return new InputMethodSubtype(this);
        }
     }

     private static InputMethodSubtypeBuilder getBuilder(int nameId, int iconId, String locale,
             String mode, String extraValue, boolean isAuxiliary,
             boolean overridesImplicitlyEnabledSubtype, int id, boolean isAsciiCapable) {
         final InputMethodSubtypeBuilder builder = new InputMethodSubtypeBuilder();
         builder.mSubtypeNameResId = nameId;
         builder.mSubtypeIconResId = iconId;
         builder.mSubtypeLocale = locale;
         builder.mSubtypeMode = mode;
         builder.mSubtypeExtraValue = extraValue;
         builder.mIsAuxiliary = isAuxiliary;
         builder.mOverridesImplicitlyEnabledSubtype = overridesImplicitlyEnabledSubtype;
         builder.mSubtypeId = id;
         builder.mIsAsciiCapable = isAsciiCapable;
         return builder;
     }

    /**
     * Constructor with no subtype ID specified.
     * @deprecated use {@link InputMethodSubtypeBuilder} instead.
     * Arguments for this constructor have the same meanings as
     * {@link InputMethodSubtype#InputMethodSubtype(int, int, String, String, String, boolean,
     * boolean, int)} except "id".
     */
    @Deprecated
    public InputMethodSubtype(int nameId, int iconId, String locale, String mode, String extraValue,
            boolean isAuxiliary, boolean overridesImplicitlyEnabledSubtype) {
        this(nameId, iconId, locale, mode, extraValue, isAuxiliary,
                overridesImplicitlyEnabledSubtype, 0);
    }

    /**
     * Constructor.
     * @deprecated use {@link InputMethodSubtypeBuilder} instead.
     * "isAsciiCapable" is "false" in this constructor.
     * @param nameId Resource ID of the subtype name string. The string resource may have exactly
     * one %s in it. If there is, the %s part will be replaced with the locale's display name by
     * the formatter. Please refer to {@link #getDisplayName} for details.
     * @param iconId Resource ID of the subtype icon drawable.
     * @param locale The locale supported by the subtype
     * @param mode The mode supported by the subtype
     * @param extraValue The extra value of the subtype. This string is free-form, but the API
     * supplies tools to deal with a key-value comma-separated list; see
     * {@link #containsExtraValueKey} and {@link #getExtraValueOf}.
     * @param isAuxiliary true when this subtype is auxiliary, false otherwise. An auxiliary
     * subtype will not be shown in the list of enabled IMEs for choosing the current IME in
     * the Settings even when this subtype is enabled. Please note that this subtype will still
     * be shown in the list of IMEs in the IME switcher to allow the user to tentatively switch
     * to this subtype while an IME is shown. The framework will never switch the current IME to
     * this subtype by {@link android.view.inputmethod.InputMethodManager#switchToLastInputMethod}.
     * The intent of having this flag is to allow for IMEs that are invoked in a one-shot way as
     * auxiliary input mode, and return to the previous IME once it is finished (e.g. voice input).
     * @param overridesImplicitlyEnabledSubtype true when this subtype should be enabled by default
     * if no other subtypes in the IME are enabled explicitly. Note that a subtype with this
     * parameter being true will not be shown in the list of subtypes in each IME's subtype enabler.
     * Having an "automatic" subtype is an example use of this flag.
     * @param id The unique ID for the subtype. The input method framework keeps track of enabled
     * subtypes by ID. When the IME package gets upgraded, enabled IDs will stay enabled even if
     * other attributes are different. If the ID is unspecified or 0,
     * Arrays.hashCode(new Object[] {locale, mode, extraValue,
     * isAuxiliary, overridesImplicitlyEnabledSubtype, isAsciiCapable}) will be used instead.
     */
    @Deprecated
    public InputMethodSubtype(int nameId, int iconId, String locale, String mode, String extraValue,
            boolean isAuxiliary, boolean overridesImplicitlyEnabledSubtype, int id) {
        this(getBuilder(nameId, iconId, locale, mode, extraValue, isAuxiliary,
                overridesImplicitlyEnabledSubtype, id, false));
    }

    /**
     * Constructor.
     * @param builder Builder for InputMethodSubtype
     */
    private InputMethodSubtype(InputMethodSubtypeBuilder builder) {
        mSubtypeNameResId = builder.mSubtypeNameResId;
        mSubtypeIconResId = builder.mSubtypeIconResId;
        mSubtypeLocale = builder.mSubtypeLocale;
        mSubtypeLanguageTag = builder.mSubtypeLanguageTag;
        mSubtypeMode = builder.mSubtypeMode;
        mSubtypeExtraValue = builder.mSubtypeExtraValue;
        mIsAuxiliary = builder.mIsAuxiliary;
        mOverridesImplicitlyEnabledSubtype = builder.mOverridesImplicitlyEnabledSubtype;
        mSubtypeId = builder.mSubtypeId;
        mIsAsciiCapable = builder.mIsAsciiCapable;
        // If hashCode() of this subtype is 0 and you want to specify it as an id of this subtype,
        // just specify 0 as this subtype's id. Then, this subtype's id is treated as 0.
        if (mSubtypeId != SUBTYPE_ID_NONE) {
            mSubtypeHashCode = mSubtypeId;
        } else {
            mSubtypeHashCode = hashCodeInternal(mSubtypeLocale, mSubtypeMode, mSubtypeExtraValue,
                    mIsAuxiliary, mOverridesImplicitlyEnabledSubtype, mIsAsciiCapable);
        }
    }

    InputMethodSubtype(Parcel source) {
        String s;
        mSubtypeNameResId = source.readInt();
        mSubtypeIconResId = source.readInt();
        s = source.readString();
        mSubtypeLocale = s != null ? s : "";
        s = source.readString();
        mSubtypeLanguageTag = s != null ? s : LANGUAGE_TAG_NONE;
        s = source.readString();
        mSubtypeMode = s != null ? s : "";
        s = source.readString();
        mSubtypeExtraValue = s != null ? s : "";
        mIsAuxiliary = (source.readInt() == 1);
        mOverridesImplicitlyEnabledSubtype = (source.readInt() == 1);
        mSubtypeHashCode = source.readInt();
        mSubtypeId = source.readInt();
        mIsAsciiCapable = (source.readInt() == 1);
    }

    /**
     * @return Resource ID of the subtype name string.
     */
    public int getNameResId() {
        return mSubtypeNameResId;
    }

    /**
     * @return Resource ID of the subtype icon drawable.
     */
    public int getIconResId() {
        return mSubtypeIconResId;
    }

    /**
     * @return The locale of the subtype. This method returns the "locale" string parameter passed
     * to the constructor.
     *
     * @deprecated Use {@link #getLanguageTag()} instead.
     */
    @Deprecated
    @NonNull
    public String getLocale() {
        return mSubtypeLocale;
    }

    /**
     * @return the BCP-47 Language Tag of the subtype.  Returns an empty string when no Language Tag
     * is specified.
     *
     * @see Locale#forLanguageTag(String)
     */
    @NonNull
    public String getLanguageTag() {
        return mSubtypeLanguageTag;
    }

    /**
     * @return {@link Locale} constructed from {@link #getLanguageTag()}. If the Language Tag is not
     * specified, then try to construct from {@link #getLocale()}
     *
     * <p>TODO: Consider to make this a public API, or move this to support lib.</p>
     * @hide
     */
    @Nullable
    public Locale getLocaleObject() {
        if (mCachedLocaleObj != null) {
            return mCachedLocaleObj;
        }
        synchronized (mLock) {
            if (mCachedLocaleObj != null) {
                return mCachedLocaleObj;
            }
            if (!TextUtils.isEmpty(mSubtypeLanguageTag)) {
                mCachedLocaleObj = Locale.forLanguageTag(mSubtypeLanguageTag);
            } else {
                mCachedLocaleObj = InputMethodUtils.constructLocaleFromString(mSubtypeLocale);
            }
            return mCachedLocaleObj;
        }
    }

    /**
     * @return The mode of the subtype.
     */
    public String getMode() {
        return mSubtypeMode;
    }

    /**
     * @return The extra value of the subtype.
     */
    public String getExtraValue() {
        return mSubtypeExtraValue;
    }

    /**
     * @return true if this subtype is auxiliary, false otherwise. An auxiliary subtype will not be
     * shown in the list of enabled IMEs for choosing the current IME in the Settings even when this
     * subtype is enabled. Please note that this subtype will still be shown in the list of IMEs in
     * the IME switcher to allow the user to tentatively switch to this subtype while an IME is
     * shown. The framework will never switch the current IME to this subtype by
     * {@link android.view.inputmethod.InputMethodManager#switchToLastInputMethod}.
     * The intent of having this flag is to allow for IMEs that are invoked in a one-shot way as
     * auxiliary input mode, and return to the previous IME once it is finished (e.g. voice input).
     */
    public boolean isAuxiliary() {
        return mIsAuxiliary;
    }

    /**
     * @return true when this subtype will be enabled by default if no other subtypes in the IME
     * are enabled explicitly, false otherwise. Note that a subtype with this method returning true
     * will not be shown in the list of subtypes in each IME's subtype enabler. Having an
     * "automatic" subtype is an example use of this flag.
     */
    public boolean overridesImplicitlyEnabledSubtype() {
        return mOverridesImplicitlyEnabledSubtype;
    }

    /**
     * @return true if this subtype is Ascii capable, false otherwise. If the subtype is ASCII
     * capable, it should guarantee that the user can input ASCII characters with this subtype.
     * This is important because many password fields only allow ASCII-characters.
     */
    public boolean isAsciiCapable() {
        return mIsAsciiCapable;
    }

    /**
     * Returns a display name for this subtype.
     *
     * <p>If {@code subtypeNameResId} is specified (!= 0) text generated from that resource will
     * be returned. The localized string resource of the label should be capitalized for inclusion
     * in UI lists. The string resource may contain at most one {@code %s}. If present, the
     * {@code %s} will be replaced with the display name of the subtype locale in the user's locale.
     *
     * <p>If {@code subtypeNameResId} is not specified (== 0) the framework returns the display name
     * of the subtype locale, as capitalized for use in UI lists, in the user's locale.
     *
     * @param context {@link Context} will be used for getting {@link Locale} and
     * {@link android.content.pm.PackageManager}.
     * @param packageName The package name of the input method.
     * @param appInfo The {@link ApplicationInfo} of the input method.
     * @return a display name for this subtype.
     */
    @NonNull
    public CharSequence getDisplayName(
            Context context, String packageName, ApplicationInfo appInfo) {
        if (mSubtypeNameResId == 0) {
            return getLocaleDisplayName(getLocaleFromContext(context), getLocaleObject(),
                    DisplayContext.CAPITALIZATION_FOR_UI_LIST_OR_MENU);
        }

        final CharSequence subtypeName = context.getPackageManager().getText(
                packageName, mSubtypeNameResId, appInfo);
        if (TextUtils.isEmpty(subtypeName)) {
            return "";
        }
        final String subtypeNameString = subtypeName.toString();
        String replacementString;
        if (containsExtraValueKey(EXTRA_KEY_UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME)) {
            replacementString = getExtraValueOf(
                    EXTRA_KEY_UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME);
        } else {
            final DisplayContext displayContext;
            if (TextUtils.equals(subtypeNameString, "%s")) {
                displayContext = DisplayContext.CAPITALIZATION_FOR_UI_LIST_OR_MENU;
            } else if (subtypeNameString.startsWith("%s")) {
                displayContext = DisplayContext.CAPITALIZATION_FOR_BEGINNING_OF_SENTENCE;
            } else {
                displayContext = DisplayContext.CAPITALIZATION_FOR_MIDDLE_OF_SENTENCE;
            }
            replacementString = getLocaleDisplayName(getLocaleFromContext(context),
                    getLocaleObject(), displayContext);
        }
        if (replacementString == null) {
            replacementString = "";
        }
        try {
            return String.format(subtypeNameString, replacementString);
        } catch (IllegalFormatException e) {
            Slog.w(TAG, "Found illegal format in subtype name("+ subtypeName + "): " + e);
            return "";
        }
    }

    @Nullable
    private static Locale getLocaleFromContext(@Nullable final Context context) {
        if (context == null) {
            return null;
        }
        if (context.getResources() == null) {
            return null;
        }
        final Configuration configuration = context.getResources().getConfiguration();
        if (configuration == null) {
            return null;
        }
        return configuration.getLocales().get(0);
    }

    /**
     * @param displayLocale {@link Locale} to be used to display {@code localeToDisplay}
     * @param localeToDisplay {@link Locale} to be displayed in {@code displayLocale}
     * @param displayContext context parameter to be used to display {@code localeToDisplay} in
     * {@code displayLocale}
     * @return Returns the name of the {@code localeToDisplay} in the user's current locale.
     */
    @NonNull
    private static String getLocaleDisplayName(
            @Nullable Locale displayLocale, @Nullable Locale localeToDisplay,
            final DisplayContext displayContext) {
        if (localeToDisplay == null) {
            return "";
        }
        final Locale nonNullDisplayLocale =
                displayLocale != null ? displayLocale : Locale.getDefault();
        return LocaleDisplayNames
                .getInstance(nonNullDisplayLocale, displayContext)
                .localeDisplayName(localeToDisplay);
    }

    private HashMap<String, String> getExtraValueHashMap() {
        synchronized (this) {
            HashMap<String, String> extraValueMap = mExtraValueHashMapCache;
            if (extraValueMap != null) {
                return extraValueMap;
            }
            extraValueMap = new HashMap<>();
            final String[] pairs = mSubtypeExtraValue.split(EXTRA_VALUE_PAIR_SEPARATOR);
            for (int i = 0; i < pairs.length; ++i) {
                final String[] pair = pairs[i].split(EXTRA_VALUE_KEY_VALUE_SEPARATOR);
                if (pair.length == 1) {
                    extraValueMap.put(pair[0], null);
                } else if (pair.length > 1) {
                    if (pair.length > 2) {
                        Slog.w(TAG, "ExtraValue has two or more '='s");
                    }
                    extraValueMap.put(pair[0], pair[1]);
                }
            }
            mExtraValueHashMapCache = extraValueMap;
            return extraValueMap;
        }
    }

    /**
     * The string of ExtraValue in subtype should be defined as follows:
     * example: key0,key1=value1,key2,key3,key4=value4
     * @param key The key of extra value
     * @return The subtype contains specified the extra value
     */
    public boolean containsExtraValueKey(String key) {
        return getExtraValueHashMap().containsKey(key);
    }

    /**
     * The string of ExtraValue in subtype should be defined as follows:
     * example: key0,key1=value1,key2,key3,key4=value4
     * @param key The key of extra value
     * @return The value of the specified key
     */
    public String getExtraValueOf(String key) {
        return getExtraValueHashMap().get(key);
    }

    @Override
    public int hashCode() {
        return mSubtypeHashCode;
    }

    /**
     * @hide
     * @return {@code true} if a valid subtype ID exists.
     */
    public final boolean hasSubtypeId() {
        return mSubtypeId != SUBTYPE_ID_NONE;
    }

    /**
     * @hide
     * @return subtype ID. {@code 0} means that not subtype ID is specified.
     */
    public final int getSubtypeId() {
        return mSubtypeId;
    }

    @Override
    public boolean equals(Object o) {
        if (o instanceof InputMethodSubtype) {
            InputMethodSubtype subtype = (InputMethodSubtype) o;
            if (subtype.mSubtypeId != 0 || mSubtypeId != 0) {
                return (subtype.hashCode() == hashCode());
            }
            return (subtype.hashCode() == hashCode())
                    && (subtype.getLocale().equals(getLocale()))
                    && (subtype.getLanguageTag().equals(getLanguageTag()))
                    && (subtype.getMode().equals(getMode()))
                    && (subtype.getExtraValue().equals(getExtraValue()))
                    && (subtype.isAuxiliary() == isAuxiliary())
                    && (subtype.overridesImplicitlyEnabledSubtype()
                            == overridesImplicitlyEnabledSubtype())
                    && (subtype.isAsciiCapable() == isAsciiCapable());
        }
        return false;
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int parcelableFlags) {
        dest.writeInt(mSubtypeNameResId);
        dest.writeInt(mSubtypeIconResId);
        dest.writeString(mSubtypeLocale);
        dest.writeString(mSubtypeLanguageTag);
        dest.writeString(mSubtypeMode);
        dest.writeString(mSubtypeExtraValue);
        dest.writeInt(mIsAuxiliary ? 1 : 0);
        dest.writeInt(mOverridesImplicitlyEnabledSubtype ? 1 : 0);
        dest.writeInt(mSubtypeHashCode);
        dest.writeInt(mSubtypeId);
        dest.writeInt(mIsAsciiCapable ? 1 : 0);
    }

    public static final Parcelable.Creator<InputMethodSubtype> CREATOR
            = new Parcelable.Creator<InputMethodSubtype>() {
        @Override
        public InputMethodSubtype createFromParcel(Parcel source) {
            return new InputMethodSubtype(source);
        }

        @Override
        public InputMethodSubtype[] newArray(int size) {
            return new InputMethodSubtype[size];
        }
    };

    private static int hashCodeInternal(String locale, String mode, String extraValue,
            boolean isAuxiliary, boolean overridesImplicitlyEnabledSubtype,
            boolean isAsciiCapable) {
        // CAVEAT: Must revisit how to compute needsToCalculateCompatibleHashCode when a new
        // attribute is added in order to avoid enabled subtypes being unexpectedly disabled.
        final boolean needsToCalculateCompatibleHashCode = !isAsciiCapable;
        if (needsToCalculateCompatibleHashCode) {
            return Arrays.hashCode(new Object[] {locale, mode, extraValue, isAuxiliary,
                    overridesImplicitlyEnabledSubtype});
        }
        return Arrays.hashCode(new Object[] {locale, mode, extraValue, isAuxiliary,
                overridesImplicitlyEnabledSubtype, isAsciiCapable});
    }

    /**
     * Sort the list of InputMethodSubtype
     * @param context Context will be used for getting localized strings from IME
     * @param flags Flags for the sort order
     * @param imi InputMethodInfo of which subtypes are subject to be sorted
     * @param subtypeList List of InputMethodSubtype which will be sorted
     * @return Sorted list of subtypes
     * @hide
     */
    public static List<InputMethodSubtype> sort(Context context, int flags, InputMethodInfo imi,
            List<InputMethodSubtype> subtypeList) {
        if (imi == null) return subtypeList;
        final HashSet<InputMethodSubtype> inputSubtypesSet = new HashSet<InputMethodSubtype>(
                subtypeList);
        final ArrayList<InputMethodSubtype> sortedList = new ArrayList<InputMethodSubtype>();
        int N = imi.getSubtypeCount();
        for (int i = 0; i < N; ++i) {
            InputMethodSubtype subtype = imi.getSubtypeAt(i);
            if (inputSubtypesSet.contains(subtype)) {
                sortedList.add(subtype);
                inputSubtypesSet.remove(subtype);
            }
        }
        // If subtypes in inputSubtypesSet remain, that means these subtypes are not
        // contained in imi, so the remaining subtypes will be appended.
        for (InputMethodSubtype subtype: inputSubtypesSet) {
            sortedList.add(subtype);
        }
        return sortedList;
    }
}