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.content.Context;
     20 import android.content.pm.ApplicationInfo;
     21 import android.os.Parcel;
     22 import android.os.Parcelable;
     23 import android.text.TextUtils;
     24 import android.util.Slog;
     25 
     26 import java.util.ArrayList;
     27 import java.util.Arrays;
     28 import java.util.HashMap;
     29 import java.util.HashSet;
     30 import java.util.List;
     31 import java.util.Locale;
     32 
     33 /**
     34  * This class is used to specify meta information of a subtype contained in a spell checker.
     35  * Subtype can describe locale (e.g. en_US, fr_FR...) used for settings.
     36  */
     37 public final class SpellCheckerSubtype implements Parcelable {
     38     private static final String TAG = SpellCheckerSubtype.class.getSimpleName();
     39     private static final String EXTRA_VALUE_PAIR_SEPARATOR = ",";
     40     private static final String EXTRA_VALUE_KEY_VALUE_SEPARATOR = "=";
     41 
     42     private final int mSubtypeHashCode;
     43     private final int mSubtypeNameResId;
     44     private final String mSubtypeLocale;
     45     private final String mSubtypeExtraValue;
     46     private HashMap<String, String> mExtraValueHashMapCache;
     47 
     48     /**
     49      * Constructor
     50      * @param nameId The name of the subtype
     51      * @param locale The locale supported by the subtype
     52      * @param extraValue The extra value of the subtype
     53      */
     54     public SpellCheckerSubtype(int nameId, String locale, String extraValue) {
     55         mSubtypeNameResId = nameId;
     56         mSubtypeLocale = locale != null ? locale : "";
     57         mSubtypeExtraValue = extraValue != null ? extraValue : "";
     58         mSubtypeHashCode = hashCodeInternal(mSubtypeLocale, mSubtypeExtraValue);
     59     }
     60 
     61     SpellCheckerSubtype(Parcel source) {
     62         String s;
     63         mSubtypeNameResId = source.readInt();
     64         s = source.readString();
     65         mSubtypeLocale = s != null ? s : "";
     66         s = source.readString();
     67         mSubtypeExtraValue = s != null ? s : "";
     68         mSubtypeHashCode = hashCodeInternal(mSubtypeLocale, mSubtypeExtraValue);
     69     }
     70 
     71     /**
     72      * @return the name of the subtype
     73      */
     74     public int getNameResId() {
     75         return mSubtypeNameResId;
     76     }
     77 
     78     /**
     79      * @return the locale of the subtype
     80      */
     81     public String getLocale() {
     82         return mSubtypeLocale;
     83     }
     84 
     85     /**
     86      * @return the extra value of the subtype
     87      */
     88     public String getExtraValue() {
     89         return mSubtypeExtraValue;
     90     }
     91 
     92     private HashMap<String, String> getExtraValueHashMap() {
     93         if (mExtraValueHashMapCache == null) {
     94             mExtraValueHashMapCache = new HashMap<String, String>();
     95             final String[] pairs = mSubtypeExtraValue.split(EXTRA_VALUE_PAIR_SEPARATOR);
     96             final int N = pairs.length;
     97             for (int i = 0; i < N; ++i) {
     98                 final String[] pair = pairs[i].split(EXTRA_VALUE_KEY_VALUE_SEPARATOR);
     99                 if (pair.length == 1) {
    100                     mExtraValueHashMapCache.put(pair[0], null);
    101                 } else if (pair.length > 1) {
    102                     if (pair.length > 2) {
    103                         Slog.w(TAG, "ExtraValue has two or more '='s");
    104                     }
    105                     mExtraValueHashMapCache.put(pair[0], pair[1]);
    106                 }
    107             }
    108         }
    109         return mExtraValueHashMapCache;
    110     }
    111 
    112     /**
    113      * The string of ExtraValue in subtype should be defined as follows:
    114      * example: key0,key1=value1,key2,key3,key4=value4
    115      * @param key the key of extra value
    116      * @return the subtype contains specified the extra value
    117      */
    118     public boolean containsExtraValueKey(String key) {
    119         return getExtraValueHashMap().containsKey(key);
    120     }
    121 
    122     /**
    123      * The string of ExtraValue in subtype should be defined as follows:
    124      * example: key0,key1=value1,key2,key3,key4=value4
    125      * @param key the key of extra value
    126      * @return the value of the specified key
    127      */
    128     public String getExtraValueOf(String key) {
    129         return getExtraValueHashMap().get(key);
    130     }
    131 
    132     @Override
    133     public int hashCode() {
    134         return mSubtypeHashCode;
    135     }
    136 
    137     @Override
    138     public boolean equals(Object o) {
    139         if (o instanceof SpellCheckerSubtype) {
    140             SpellCheckerSubtype subtype = (SpellCheckerSubtype) o;
    141             return (subtype.hashCode() == hashCode())
    142                 && (subtype.getNameResId() == getNameResId())
    143                 && (subtype.getLocale().equals(getLocale()))
    144                 && (subtype.getExtraValue().equals(getExtraValue()));
    145         }
    146         return false;
    147     }
    148 
    149     /**
    150      * @hide
    151      */
    152     public static Locale constructLocaleFromString(String localeStr) {
    153         if (TextUtils.isEmpty(localeStr))
    154             return null;
    155         String[] localeParams = localeStr.split("_", 3);
    156         // The length of localeStr is guaranteed to always return a 1 <= value <= 3
    157         // because localeStr is not empty.
    158         if (localeParams.length == 1) {
    159             return new Locale(localeParams[0]);
    160         } else if (localeParams.length == 2) {
    161             return new Locale(localeParams[0], localeParams[1]);
    162         } else if (localeParams.length == 3) {
    163             return new Locale(localeParams[0], localeParams[1], localeParams[2]);
    164         }
    165         return null;
    166     }
    167 
    168     /**
    169      * @param context Context will be used for getting Locale and PackageManager.
    170      * @param packageName The package name of the spell checker
    171      * @param appInfo The application info of the spell checker
    172      * @return a display name for this subtype. The string resource of the label (mSubtypeNameResId)
    173      * can have only one %s in it. If there is, the %s part will be replaced with the locale's
    174      * display name by the formatter. If there is not, this method simply returns the string
    175      * specified by mSubtypeNameResId. If mSubtypeNameResId is not specified (== 0), it's up to the
    176      * framework to generate an appropriate display name.
    177      */
    178     public CharSequence getDisplayName(
    179             Context context, String packageName, ApplicationInfo appInfo) {
    180         final Locale locale = constructLocaleFromString(mSubtypeLocale);
    181         final String localeStr = locale != null ? locale.getDisplayName() : mSubtypeLocale;
    182         if (mSubtypeNameResId == 0) {
    183             return localeStr;
    184         }
    185         final CharSequence subtypeName = context.getPackageManager().getText(
    186                 packageName, mSubtypeNameResId, appInfo);
    187         if (!TextUtils.isEmpty(subtypeName)) {
    188             return String.format(subtypeName.toString(), localeStr);
    189         } else {
    190             return localeStr;
    191         }
    192     }
    193 
    194     @Override
    195     public int describeContents() {
    196         return 0;
    197     }
    198 
    199     @Override
    200     public void writeToParcel(Parcel dest, int parcelableFlags) {
    201         dest.writeInt(mSubtypeNameResId);
    202         dest.writeString(mSubtypeLocale);
    203         dest.writeString(mSubtypeExtraValue);
    204     }
    205 
    206     public static final Parcelable.Creator<SpellCheckerSubtype> CREATOR
    207             = new Parcelable.Creator<SpellCheckerSubtype>() {
    208         @Override
    209         public SpellCheckerSubtype createFromParcel(Parcel source) {
    210             return new SpellCheckerSubtype(source);
    211         }
    212 
    213         @Override
    214         public SpellCheckerSubtype[] newArray(int size) {
    215             return new SpellCheckerSubtype[size];
    216         }
    217     };
    218 
    219     private static int hashCodeInternal(String locale, String extraValue) {
    220         return Arrays.hashCode(new Object[] {locale, extraValue});
    221     }
    222 
    223     /**
    224      * Sort the list of subtypes
    225      * @param context Context will be used for getting localized strings
    226      * @param flags Flags for the sort order
    227      * @param sci SpellCheckerInfo of which subtypes are subject to be sorted
    228      * @param subtypeList List which will be sorted
    229      * @return Sorted list of subtypes
    230      * @hide
    231      */
    232     public static List<SpellCheckerSubtype> sort(Context context, int flags, SpellCheckerInfo sci,
    233             List<SpellCheckerSubtype> subtypeList) {
    234         if (sci == null) return subtypeList;
    235         final HashSet<SpellCheckerSubtype> subtypesSet = new HashSet<SpellCheckerSubtype>(
    236                 subtypeList);
    237         final ArrayList<SpellCheckerSubtype> sortedList = new ArrayList<SpellCheckerSubtype>();
    238         int N = sci.getSubtypeCount();
    239         for (int i = 0; i < N; ++i) {
    240             SpellCheckerSubtype subtype = sci.getSubtypeAt(i);
    241             if (subtypesSet.contains(subtype)) {
    242                 sortedList.add(subtype);
    243                 subtypesSet.remove(subtype);
    244             }
    245         }
    246         // If subtypes in subtypesSet remain, that means these subtypes are not
    247         // contained in sci, so the remaining subtypes will be appended.
    248         for (SpellCheckerSubtype subtype: subtypesSet) {
    249             sortedList.add(subtype);
    250         }
    251         return sortedList;
    252     }
    253 }
    254