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