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