Home | History | Annotate | Download | only in textservice
      1 /*
      2  * Copyright (C) 2011 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.textservice;
     18 
     19 import android.annotation.NonNull;
     20 import android.annotation.Nullable;
     21 import android.content.Context;
     22 import android.content.pm.ApplicationInfo;
     23 import android.os.Parcel;
     24 import android.os.Parcelable;
     25 import android.text.TextUtils;
     26 import android.util.Slog;
     27 
     28 import com.android.internal.inputmethod.InputMethodUtils;
     29 
     30 import java.util.ArrayList;
     31 import java.util.Arrays;
     32 import java.util.HashMap;
     33 import java.util.HashSet;
     34 import java.util.List;
     35 import java.util.Locale;
     36 
     37 /**
     38  * This class is used to specify meta information of a subtype contained in a spell checker.
     39  * Subtype can describe locale (e.g. en_US, fr_FR...) used for settings.
     40  *
     41  * @see SpellCheckerInfo
     42  *
     43  * @attr ref android.R.styleable#SpellChecker_Subtype_label
     44  * @attr ref android.R.styleable#SpellChecker_Subtype_languageTag
     45  * @attr ref android.R.styleable#SpellChecker_Subtype_subtypeLocale
     46  * @attr ref android.R.styleable#SpellChecker_Subtype_subtypeExtraValue
     47  * @attr ref android.R.styleable#SpellChecker_Subtype_subtypeId
     48  */
     49 public final class SpellCheckerSubtype implements Parcelable {
     50     private static final String TAG = SpellCheckerSubtype.class.getSimpleName();
     51     private static final String EXTRA_VALUE_PAIR_SEPARATOR = ",";
     52     private static final String EXTRA_VALUE_KEY_VALUE_SEPARATOR = "=";
     53     /**
     54      * @hide
     55      */
     56     public static final int SUBTYPE_ID_NONE = 0;
     57     private static final String SUBTYPE_LANGUAGE_TAG_NONE = "";
     58 
     59     private final int mSubtypeId;
     60     private final int mSubtypeHashCode;
     61     private final int mSubtypeNameResId;
     62     private final String mSubtypeLocale;
     63     private final String mSubtypeLanguageTag;
     64     private final String mSubtypeExtraValue;
     65     private HashMap<String, String> mExtraValueHashMapCache;
     66 
     67     /**
     68      * Constructor.
     69      *
     70      * <p>There is no public API that requires developers to instantiate custom
     71      * {@link SpellCheckerSubtype} object.  Hence so far there is no need to make this constructor
     72      * available in public API.</p>
     73      *
     74      * @param nameId The name of the subtype
     75      * @param locale The locale supported by the subtype
     76      * @param languageTag The BCP-47 Language Tag associated with this subtype.
     77      * @param extraValue The extra value of the subtype
     78      * @param subtypeId The subtype ID that is supposed to be stable during package update.
     79      *
     80      * @hide
     81      */
     82     public SpellCheckerSubtype(int nameId, String locale, String languageTag, String extraValue,
     83             int subtypeId) {
     84         mSubtypeNameResId = nameId;
     85         mSubtypeLocale = locale != null ? locale : "";
     86         mSubtypeLanguageTag = languageTag != null ? languageTag : SUBTYPE_LANGUAGE_TAG_NONE;
     87         mSubtypeExtraValue = extraValue != null ? extraValue : "";
     88         mSubtypeId = subtypeId;
     89         mSubtypeHashCode = mSubtypeId != SUBTYPE_ID_NONE ?
     90                 mSubtypeId : hashCodeInternal(mSubtypeLocale, mSubtypeExtraValue);
     91     }
     92 
     93     /**
     94      * Constructor.
     95      * @param nameId The name of the subtype
     96      * @param locale The locale supported by the subtype
     97      * @param extraValue The extra value of the subtype
     98      *
     99      * @deprecated There is no public API that requires developers to directly instantiate custom
    100      * {@link SpellCheckerSubtype} objects right now.  Hence only the system is expected to be able
    101      * to instantiate {@link SpellCheckerSubtype} object.
    102      */
    103     @Deprecated
    104     public SpellCheckerSubtype(int nameId, String locale, String extraValue) {
    105         this(nameId, locale, SUBTYPE_LANGUAGE_TAG_NONE, extraValue, SUBTYPE_ID_NONE);
    106     }
    107 
    108     SpellCheckerSubtype(Parcel source) {
    109         String s;
    110         mSubtypeNameResId = source.readInt();
    111         s = source.readString();
    112         mSubtypeLocale = s != null ? s : "";
    113         s = source.readString();
    114         mSubtypeLanguageTag = s != null ? s : "";
    115         s = source.readString();
    116         mSubtypeExtraValue = s != null ? s : "";
    117         mSubtypeId = source.readInt();
    118         mSubtypeHashCode = mSubtypeId != SUBTYPE_ID_NONE ?
    119                 mSubtypeId : hashCodeInternal(mSubtypeLocale, mSubtypeExtraValue);
    120     }
    121 
    122     /**
    123      * @return the name of the subtype
    124      */
    125     public int getNameResId() {
    126         return mSubtypeNameResId;
    127     }
    128 
    129     /**
    130      * @return the locale of the subtype
    131      *
    132      * @deprecated Use {@link #getLanguageTag()} instead.
    133      */
    134     @Deprecated
    135     @NonNull
    136     public String getLocale() {
    137         return mSubtypeLocale;
    138     }
    139 
    140     /**
    141      * @return the BCP-47 Language Tag of the subtype.  Returns an empty string when no Language Tag
    142      * is specified.
    143      *
    144      * @see Locale#forLanguageTag(String)
    145      */
    146     @NonNull
    147     public String getLanguageTag() {
    148         return mSubtypeLanguageTag;
    149     }
    150 
    151     /**
    152      * @return the extra value of the subtype
    153      */
    154     public String getExtraValue() {
    155         return mSubtypeExtraValue;
    156     }
    157 
    158     private HashMap<String, String> getExtraValueHashMap() {
    159         if (mExtraValueHashMapCache == null) {
    160             mExtraValueHashMapCache = new HashMap<String, String>();
    161             final String[] pairs = mSubtypeExtraValue.split(EXTRA_VALUE_PAIR_SEPARATOR);
    162             final int N = pairs.length;
    163             for (int i = 0; i < N; ++i) {
    164                 final String[] pair = pairs[i].split(EXTRA_VALUE_KEY_VALUE_SEPARATOR);
    165                 if (pair.length == 1) {
    166                     mExtraValueHashMapCache.put(pair[0], null);
    167                 } else if (pair.length > 1) {
    168                     if (pair.length > 2) {
    169                         Slog.w(TAG, "ExtraValue has two or more '='s");
    170                     }
    171                     mExtraValueHashMapCache.put(pair[0], pair[1]);
    172                 }
    173             }
    174         }
    175         return mExtraValueHashMapCache;
    176     }
    177 
    178     /**
    179      * The string of ExtraValue in subtype should be defined as follows:
    180      * example: key0,key1=value1,key2,key3,key4=value4
    181      * @param key the key of extra value
    182      * @return the subtype contains specified the extra value
    183      */
    184     public boolean containsExtraValueKey(String key) {
    185         return getExtraValueHashMap().containsKey(key);
    186     }
    187 
    188     /**
    189      * The string of ExtraValue in subtype should be defined as follows:
    190      * example: key0,key1=value1,key2,key3,key4=value4
    191      * @param key the key of extra value
    192      * @return the value of the specified key
    193      */
    194     public String getExtraValueOf(String key) {
    195         return getExtraValueHashMap().get(key);
    196     }
    197 
    198     @Override
    199     public int hashCode() {
    200         return mSubtypeHashCode;
    201     }
    202 
    203     @Override
    204     public boolean equals(Object o) {
    205         if (o instanceof SpellCheckerSubtype) {
    206             SpellCheckerSubtype subtype = (SpellCheckerSubtype) o;
    207             if (subtype.mSubtypeId != SUBTYPE_ID_NONE || mSubtypeId != SUBTYPE_ID_NONE) {
    208                 return (subtype.hashCode() == hashCode());
    209             }
    210             return (subtype.hashCode() == hashCode())
    211                     && (subtype.getNameResId() == getNameResId())
    212                     && (subtype.getLocale().equals(getLocale()))
    213                     && (subtype.getLanguageTag().equals(getLanguageTag()))
    214                     && (subtype.getExtraValue().equals(getExtraValue()));
    215         }
    216         return false;
    217     }
    218 
    219     /**
    220      * @return {@link Locale} constructed from {@link #getLanguageTag()}. If the Language Tag is not
    221      * specified, then try to construct from {@link #getLocale()}
    222      *
    223      * <p>TODO: Consider to make this a public API, or move this to support lib.</p>
    224      * @hide
    225      */
    226     @Nullable
    227     public Locale getLocaleObject() {
    228         if (!TextUtils.isEmpty(mSubtypeLanguageTag)) {
    229             return Locale.forLanguageTag(mSubtypeLanguageTag);
    230         }
    231         return InputMethodUtils.constructLocaleFromString(mSubtypeLocale);
    232     }
    233 
    234     /**
    235      * @param context Context will be used for getting Locale and PackageManager.
    236      * @param packageName The package name of the spell checker
    237      * @param appInfo The application info of the spell checker
    238      * @return a display name for this subtype. The string resource of the label (mSubtypeNameResId)
    239      * can have only one %s in it. If there is, the %s part will be replaced with the locale's
    240      * display name by the formatter. If there is not, this method simply returns the string
    241      * specified by mSubtypeNameResId. If mSubtypeNameResId is not specified (== 0), it's up to the
    242      * framework to generate an appropriate display name.
    243      */
    244     public CharSequence getDisplayName(
    245             Context context, String packageName, ApplicationInfo appInfo) {
    246         final Locale locale = getLocaleObject();
    247         final String localeStr = locale != null ? locale.getDisplayName() : mSubtypeLocale;
    248         if (mSubtypeNameResId == 0) {
    249             return localeStr;
    250         }
    251         final CharSequence subtypeName = context.getPackageManager().getText(
    252                 packageName, mSubtypeNameResId, appInfo);
    253         if (!TextUtils.isEmpty(subtypeName)) {
    254             return String.format(subtypeName.toString(), localeStr);
    255         } else {
    256             return localeStr;
    257         }
    258     }
    259 
    260     @Override
    261     public int describeContents() {
    262         return 0;
    263     }
    264 
    265     @Override
    266     public void writeToParcel(Parcel dest, int parcelableFlags) {
    267         dest.writeInt(mSubtypeNameResId);
    268         dest.writeString(mSubtypeLocale);
    269         dest.writeString(mSubtypeLanguageTag);
    270         dest.writeString(mSubtypeExtraValue);
    271         dest.writeInt(mSubtypeId);
    272     }
    273 
    274     public static final Parcelable.Creator<SpellCheckerSubtype> CREATOR
    275             = new Parcelable.Creator<SpellCheckerSubtype>() {
    276         @Override
    277         public SpellCheckerSubtype createFromParcel(Parcel source) {
    278             return new SpellCheckerSubtype(source);
    279         }
    280 
    281         @Override
    282         public SpellCheckerSubtype[] newArray(int size) {
    283             return new SpellCheckerSubtype[size];
    284         }
    285     };
    286 
    287     private static int hashCodeInternal(String locale, String extraValue) {
    288         return Arrays.hashCode(new Object[] {locale, extraValue});
    289     }
    290 
    291     /**
    292      * Sort the list of subtypes
    293      * @param context Context will be used for getting localized strings
    294      * @param flags Flags for the sort order
    295      * @param sci SpellCheckerInfo of which subtypes are subject to be sorted
    296      * @param subtypeList List which will be sorted
    297      * @return Sorted list of subtypes
    298      * @hide
    299      */
    300     public static List<SpellCheckerSubtype> sort(Context context, int flags, SpellCheckerInfo sci,
    301             List<SpellCheckerSubtype> subtypeList) {
    302         if (sci == null) return subtypeList;
    303         final HashSet<SpellCheckerSubtype> subtypesSet = new HashSet<SpellCheckerSubtype>(
    304                 subtypeList);
    305         final ArrayList<SpellCheckerSubtype> sortedList = new ArrayList<SpellCheckerSubtype>();
    306         int N = sci.getSubtypeCount();
    307         for (int i = 0; i < N; ++i) {
    308             SpellCheckerSubtype subtype = sci.getSubtypeAt(i);
    309             if (subtypesSet.contains(subtype)) {
    310                 sortedList.add(subtype);
    311                 subtypesSet.remove(subtype);
    312             }
    313         }
    314         // If subtypes in subtypesSet remain, that means these subtypes are not
    315         // contained in sci, so the remaining subtypes will be appended.
    316         for (SpellCheckerSubtype subtype: subtypesSet) {
    317             sortedList.add(subtype);
    318         }
    319         return sortedList;
    320     }
    321 }
    322