content: /*
   * Copyright (C) 2011 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.textservice;
  
  import android.annotation.NonNull;
  import android.annotation.Nullable;
  import android.content.Context;
  import android.content.pm.ApplicationInfo;
  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.List;
  import java.util.Locale;
  
  /**
   * This class is used to specify meta information of a subtype contained in a spell checker.
   * Subtype can describe locale (e.g. en_US, fr_FR...) used for settings.
   *
   * @see SpellCheckerInfo
   *
   * @attr ref android.R.styleable#SpellChecker_Subtype_label
   * @attr ref android.R.styleable#SpellChecker_Subtype_languageTag
   * @attr ref android.R.styleable#SpellChecker_Subtype_subtypeLocale
   * @attr ref android.R.styleable#SpellChecker_Subtype_subtypeExtraValue
   * @attr ref android.R.styleable#SpellChecker_Subtype_subtypeId
   */
  public final class SpellCheckerSubtype implements Parcelable {
      private static final String TAG = SpellCheckerSubtype.class.getSimpleName();
      private static final String EXTRA_VALUE_PAIR_SEPARATOR = ",";
      private static final String EXTRA_VALUE_KEY_VALUE_SEPARATOR = "=";
      /**
       * @hide
       */
      public static final int SUBTYPE_ID_NONE = 0;
      private static final String SUBTYPE_LANGUAGE_TAG_NONE = "";
  
      private final int mSubtypeId;
      private final int mSubtypeHashCode;
      private final int mSubtypeNameResId;
      private final String mSubtypeLocale;
      private final String mSubtypeLanguageTag;
      private final String mSubtypeExtraValue;
      private HashMap<String, String> mExtraValueHashMapCache;
  
      /**
       * Constructor.
       *
       * <p>There is no public API that requires developers to instantiate custom
       * {@link SpellCheckerSubtype} object.  Hence so far there is no need to make this constructor
       * available in public API.</p>
       *
       * @param nameId The name of the subtype
       * @param locale The locale supported by the subtype
       * @param languageTag The BCP-47 Language Tag associated with this subtype.
       * @param extraValue The extra value of the subtype
       * @param subtypeId The subtype ID that is supposed to be stable during package update.
       *
       * @hide
       */
      public SpellCheckerSubtype(int nameId, String locale, String languageTag, String extraValue,
              int subtypeId) {
          mSubtypeNameResId = nameId;
          mSubtypeLocale = locale != null ? locale : "";
          mSubtypeLanguageTag = languageTag != null ? languageTag : SUBTYPE_LANGUAGE_TAG_NONE;
          mSubtypeExtraValue = extraValue != null ? extraValue : "";
          mSubtypeId = subtypeId;
          mSubtypeHashCode = mSubtypeId != SUBTYPE_ID_NONE ?
                  mSubtypeId : hashCodeInternal(mSubtypeLocale, mSubtypeExtraValue);
      }
  
      /**
       * Constructor.
       * @param nameId The name of the subtype
       * @param locale The locale supported by the subtype
       * @param extraValue The extra value of the subtype
       *
       * @deprecated There is no public API that requires developers to directly instantiate custom
       * {@link SpellCheckerSubtype} objects right now.  Hence only the system is expected to be able
       * to instantiate {@link SpellCheckerSubtype} object.
       */
      @Deprecated
      public SpellCheckerSubtype(int nameId, String locale, String extraValue) {
          this(nameId, locale, SUBTYPE_LANGUAGE_TAG_NONE, extraValue, SUBTYPE_ID_NONE);
      }
  
      SpellCheckerSubtype(Parcel source) {
          String s;
          mSubtypeNameResId = source.readInt();
          s = source.readString();
          mSubtypeLocale = s != null ? s : "";
          s = source.readString();
          mSubtypeLanguageTag = s != null ? s : "";
          s = source.readString();
          mSubtypeExtraValue = s != null ? s : "";
          mSubtypeId = source.readInt();
          mSubtypeHashCode = mSubtypeId != SUBTYPE_ID_NONE ?
                  mSubtypeId : hashCodeInternal(mSubtypeLocale, mSubtypeExtraValue);
      }
  
      /**
       * @return the name of the subtype
       */
      public int getNameResId() {
          return mSubtypeNameResId;
      }
  
      /**
       * @return the locale of the subtype
       *
       * @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 the extra value of the subtype
       */
      public String getExtraValue() {
          return mSubtypeExtraValue;
      }
  
      private HashMap<String, String> getExtraValueHashMap() {
          if (mExtraValueHashMapCache == null) {
              mExtraValueHashMapCache = new HashMap<String, String>();
              final String[] pairs = mSubtypeExtraValue.split(EXTRA_VALUE_PAIR_SEPARATOR);
              final int N = pairs.length;
              for (int i = 0; i < N; ++i) {
                  final String[] pair = pairs[i].split(EXTRA_VALUE_KEY_VALUE_SEPARATOR);
                  if (pair.length == 1) {
                      mExtraValueHashMapCache.put(pair[0], null);
                  } else if (pair.length > 1) {
                      if (pair.length > 2) {
                          Slog.w(TAG, "ExtraValue has two or more '='s");
                      }
                      mExtraValueHashMapCache.put(pair[0], pair[1]);
                  }
              }
          }
          return mExtraValueHashMapCache;
      }
  
      /**
       * 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;
      }
  
      @Override
      public boolean equals(Object o) {
          if (o instanceof SpellCheckerSubtype) {
              SpellCheckerSubtype subtype = (SpellCheckerSubtype) o;
              if (subtype.mSubtypeId != SUBTYPE_ID_NONE || mSubtypeId != SUBTYPE_ID_NONE) {
                  return (subtype.hashCode() == hashCode());
              }
              return (subtype.hashCode() == hashCode())
                      && (subtype.getNameResId() == getNameResId())
                      && (subtype.getLocale().equals(getLocale()))
                      && (subtype.getLanguageTag().equals(getLanguageTag()))
                      && (subtype.getExtraValue().equals(getExtraValue()));
          }
          return false;
      }
  
      /**
       * @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 (!TextUtils.isEmpty(mSubtypeLanguageTag)) {
              return Locale.forLanguageTag(mSubtypeLanguageTag);
          }
          return SubtypeLocaleUtils.constructLocaleFromString(mSubtypeLocale);
      }
  
      /**
       * @param context Context will be used for getting Locale and PackageManager.
       * @param packageName The package name of the spell checker
       * @param appInfo The application info of the spell checker
       * @return a display name for this subtype. The string resource of the label (mSubtypeNameResId)
       * can have only one %s in it. If there is, the %s part will be replaced with the locale's
       * display name by the formatter. If there is not, this method simply returns the string
       * specified by mSubtypeNameResId. If mSubtypeNameResId is not specified (== 0), it's up to the
       * framework to generate an appropriate display name.
       */
      public CharSequence getDisplayName(
              Context context, String packageName, ApplicationInfo appInfo) {
          final Locale locale = getLocaleObject();
          final String localeStr = locale != null ? locale.getDisplayName() : mSubtypeLocale;
          if (mSubtypeNameResId == 0) {
              return localeStr;
          }
          final CharSequence subtypeName = context.getPackageManager().getText(
                  packageName, mSubtypeNameResId, appInfo);
          if (!TextUtils.isEmpty(subtypeName)) {
              return String.format(subtypeName.toString(), localeStr);
          } else {
              return localeStr;
          }
      }
  
      @Override
      public int describeContents() {
          return 0;
      }
  
      @Override
      public void writeToParcel(Parcel dest, int parcelableFlags) {
          dest.writeInt(mSubtypeNameResId);
          dest.writeString(mSubtypeLocale);
          dest.writeString(mSubtypeLanguageTag);
          dest.writeString(mSubtypeExtraValue);
          dest.writeInt(mSubtypeId);
      }
  
      public static final @android.annotation.NonNull Parcelable.Creator<SpellCheckerSubtype> CREATOR
              = new Parcelable.Creator<SpellCheckerSubtype>() {
          @Override
          public SpellCheckerSubtype createFromParcel(Parcel source) {
              return new SpellCheckerSubtype(source);
          }
  
          @Override
          public SpellCheckerSubtype[] newArray(int size) {
              return new SpellCheckerSubtype[size];
          }
      };
  
      private static int hashCodeInternal(String locale, String extraValue) {
          return Arrays.hashCode(new Object[] {locale, extraValue});
      }
  
      /**
       * Sort the list of subtypes
       * @param context Context will be used for getting localized strings
       * @param flags Flags for the sort order
       * @param sci SpellCheckerInfo of which subtypes are subject to be sorted
       * @param subtypeList List which will be sorted
       * @return Sorted list of subtypes
       * @hide
       */
      public static List<SpellCheckerSubtype> sort(Context context, int flags, SpellCheckerInfo sci,
              List<SpellCheckerSubtype> subtypeList) {
          if (sci == null) return subtypeList;
          final HashSet<SpellCheckerSubtype> subtypesSet = new HashSet<SpellCheckerSubtype>(
                  subtypeList);
          final ArrayList<SpellCheckerSubtype> sortedList = new ArrayList<SpellCheckerSubtype>();
          int N = sci.getSubtypeCount();
          for (int i = 0; i < N; ++i) {
              SpellCheckerSubtype subtype = sci.getSubtypeAt(i);
              if (subtypesSet.contains(subtype)) {
                  sortedList.add(subtype);
                  subtypesSet.remove(subtype);
              }
          }
          // If subtypes in subtypesSet remain, that means these subtypes are not
          // contained in sci, so the remaining subtypes will be appended.
          for (SpellCheckerSubtype subtype: subtypesSet) {
              sortedList.add(subtype);
          }
          return sortedList;
      }
  }
  