content: /*
   * 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.SubtypeLocaleUtils;
  
  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 = SubtypeLocaleUtils.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 @android.annotation.NonNull 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;
      }
  }