blob: 8224e0eb48dae65276df20e9b5401869301543d3 [file] [log] [blame]
satok03b2ea12011-08-03 17:36:14 +09001/*
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
Yohei Yukawa868d19b2015-12-07 15:58:57 -080019import android.annotation.NonNull;
Yohei Yukawa658c29e2015-12-04 14:43:01 -080020import android.annotation.Nullable;
satok03b2ea12011-08-03 17:36:14 +090021import android.content.Context;
satokc7145312011-08-26 13:58:29 +090022import android.content.pm.ApplicationInfo;
satok03b2ea12011-08-03 17:36:14 +090023import android.os.Parcel;
24import android.os.Parcelable;
satokc7145312011-08-26 13:58:29 +090025import android.text.TextUtils;
satok0dc1f642011-11-18 11:27:10 +090026import android.util.Slog;
satok03b2ea12011-08-03 17:36:14 +090027
Yohei Yukawa835ab942018-09-12 16:23:26 -070028import com.android.internal.inputmethod.SubtypeLocaleUtils;
Aurimas Liutikas67e2ae82016-10-11 18:17:42 -070029
satok03b2ea12011-08-03 17:36:14 +090030import java.util.ArrayList;
31import java.util.Arrays;
satok0dc1f642011-11-18 11:27:10 +090032import java.util.HashMap;
satok03b2ea12011-08-03 17:36:14 +090033import java.util.HashSet;
34import java.util.List;
satokc7145312011-08-26 13:58:29 +090035import java.util.Locale;
satok03b2ea12011-08-03 17:36:14 +090036
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.
Yohei Yukawa08943192015-12-04 16:16:47 -080040 *
41 * @see SpellCheckerInfo
42 *
43 * @attr ref android.R.styleable#SpellChecker_Subtype_label
Yohei Yukawa868d19b2015-12-07 15:58:57 -080044 * @attr ref android.R.styleable#SpellChecker_Subtype_languageTag
Yohei Yukawa08943192015-12-04 16:16:47 -080045 * @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
satok03b2ea12011-08-03 17:36:14 +090048 */
49public final class SpellCheckerSubtype implements Parcelable {
satok0dc1f642011-11-18 11:27:10 +090050 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 = "=";
Yohei Yukawaad150ee2016-03-16 17:22:27 -070053 /**
54 * @hide
55 */
56 public static final int SUBTYPE_ID_NONE = 0;
Yohei Yukawa868d19b2015-12-07 15:58:57 -080057 private static final String SUBTYPE_LANGUAGE_TAG_NONE = "";
satok03b2ea12011-08-03 17:36:14 +090058
Yohei Yukawa08943192015-12-04 16:16:47 -080059 private final int mSubtypeId;
satok03b2ea12011-08-03 17:36:14 +090060 private final int mSubtypeHashCode;
61 private final int mSubtypeNameResId;
62 private final String mSubtypeLocale;
Yohei Yukawa868d19b2015-12-07 15:58:57 -080063 private final String mSubtypeLanguageTag;
satok03b2ea12011-08-03 17:36:14 +090064 private final String mSubtypeExtraValue;
satok0dc1f642011-11-18 11:27:10 +090065 private HashMap<String, String> mExtraValueHashMapCache;
satok03b2ea12011-08-03 17:36:14 +090066
67 /**
Yohei Yukawa08943192015-12-04 16:16:47 -080068 * 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 *
satok03b2ea12011-08-03 17:36:14 +090074 * @param nameId The name of the subtype
75 * @param locale The locale supported by the subtype
Yohei Yukawa868d19b2015-12-07 15:58:57 -080076 * @param languageTag The BCP-47 Language Tag associated with this subtype.
satok03b2ea12011-08-03 17:36:14 +090077 * @param extraValue The extra value of the subtype
Yohei Yukawa08943192015-12-04 16:16:47 -080078 * @param subtypeId The subtype ID that is supposed to be stable during package update.
79 *
80 * @hide
satok03b2ea12011-08-03 17:36:14 +090081 */
Yohei Yukawa868d19b2015-12-07 15:58:57 -080082 public SpellCheckerSubtype(int nameId, String locale, String languageTag, String extraValue,
83 int subtypeId) {
satok03b2ea12011-08-03 17:36:14 +090084 mSubtypeNameResId = nameId;
85 mSubtypeLocale = locale != null ? locale : "";
Yohei Yukawa868d19b2015-12-07 15:58:57 -080086 mSubtypeLanguageTag = languageTag != null ? languageTag : SUBTYPE_LANGUAGE_TAG_NONE;
satok03b2ea12011-08-03 17:36:14 +090087 mSubtypeExtraValue = extraValue != null ? extraValue : "";
Yohei Yukawa08943192015-12-04 16:16:47 -080088 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 */
Aurimas Liutikas514c5ef2016-05-24 15:22:55 -0700103 @Deprecated
Yohei Yukawa08943192015-12-04 16:16:47 -0800104 public SpellCheckerSubtype(int nameId, String locale, String extraValue) {
Yohei Yukawa868d19b2015-12-07 15:58:57 -0800105 this(nameId, locale, SUBTYPE_LANGUAGE_TAG_NONE, extraValue, SUBTYPE_ID_NONE);
satok03b2ea12011-08-03 17:36:14 +0900106 }
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();
Yohei Yukawa868d19b2015-12-07 15:58:57 -0800114 mSubtypeLanguageTag = s != null ? s : "";
115 s = source.readString();
satok03b2ea12011-08-03 17:36:14 +0900116 mSubtypeExtraValue = s != null ? s : "";
Yohei Yukawa08943192015-12-04 16:16:47 -0800117 mSubtypeId = source.readInt();
118 mSubtypeHashCode = mSubtypeId != SUBTYPE_ID_NONE ?
119 mSubtypeId : hashCodeInternal(mSubtypeLocale, mSubtypeExtraValue);
satok03b2ea12011-08-03 17:36:14 +0900120 }
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
Yohei Yukawa868d19b2015-12-07 15:58:57 -0800131 *
132 * @deprecated Use {@link #getLanguageTag()} instead.
satok03b2ea12011-08-03 17:36:14 +0900133 */
Yohei Yukawa868d19b2015-12-07 15:58:57 -0800134 @Deprecated
135 @NonNull
satok03b2ea12011-08-03 17:36:14 +0900136 public String getLocale() {
137 return mSubtypeLocale;
138 }
139
140 /**
Yohei Yukawa868d19b2015-12-07 15:58:57 -0800141 * @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 /**
satok03b2ea12011-08-03 17:36:14 +0900152 * @return the extra value of the subtype
153 */
154 public String getExtraValue() {
155 return mSubtypeExtraValue;
156 }
157
satok0dc1f642011-11-18 11:27:10 +0900158 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 /**
satok0dc1f642011-11-18 11:27:10 +0900179 * 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 /**
satok0dc1f642011-11-18 11:27:10 +0900189 * 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
satok03b2ea12011-08-03 17:36:14 +0900198 @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;
Yohei Yukawa08943192015-12-04 16:16:47 -0800207 if (subtype.mSubtypeId != SUBTYPE_ID_NONE || mSubtypeId != SUBTYPE_ID_NONE) {
208 return (subtype.hashCode() == hashCode());
209 }
satok03b2ea12011-08-03 17:36:14 +0900210 return (subtype.hashCode() == hashCode())
Yohei Yukawa08943192015-12-04 16:16:47 -0800211 && (subtype.getNameResId() == getNameResId())
212 && (subtype.getLocale().equals(getLocale()))
Yohei Yukawa868d19b2015-12-07 15:58:57 -0800213 && (subtype.getLanguageTag().equals(getLanguageTag()))
Yohei Yukawa08943192015-12-04 16:16:47 -0800214 && (subtype.getExtraValue().equals(getExtraValue()));
satok03b2ea12011-08-03 17:36:14 +0900215 }
216 return false;
217 }
218
satokf927e172012-05-24 16:52:54 +0900219 /**
Yohei Yukawa868d19b2015-12-07 15:58:57 -0800220 * @return {@link Locale} constructed from {@link #getLanguageTag()}. If the Language Tag is not
221 * specified, then try to construct from {@link #getLocale()}
Yohei Yukawa658c29e2015-12-04 14:43:01 -0800222 *
Yohei Yukawa868d19b2015-12-07 15:58:57 -0800223 * <p>TODO: Consider to make this a public API, or move this to support lib.</p>
satokf927e172012-05-24 16:52:54 +0900224 * @hide
225 */
Yohei Yukawa658c29e2015-12-04 14:43:01 -0800226 @Nullable
227 public Locale getLocaleObject() {
Yohei Yukawa868d19b2015-12-07 15:58:57 -0800228 if (!TextUtils.isEmpty(mSubtypeLanguageTag)) {
229 return Locale.forLanguageTag(mSubtypeLanguageTag);
230 }
Yohei Yukawa835ab942018-09-12 16:23:26 -0700231 return SubtypeLocaleUtils.constructLocaleFromString(mSubtypeLocale);
satokc7145312011-08-26 13:58:29 +0900232 }
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) {
Yohei Yukawaeae60ba2015-12-04 14:43:17 -0800246 final Locale locale = getLocaleObject();
satokc7145312011-08-26 13:58:29 +0900247 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
satok03b2ea12011-08-03 17:36:14 +0900260 @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);
Yohei Yukawa868d19b2015-12-07 15:58:57 -0800269 dest.writeString(mSubtypeLanguageTag);
satok03b2ea12011-08-03 17:36:14 +0900270 dest.writeString(mSubtypeExtraValue);
Yohei Yukawa08943192015-12-04 16:16:47 -0800271 dest.writeInt(mSubtypeId);
satok03b2ea12011-08-03 17:36:14 +0900272 }
273
Jeff Sharkey9e8f83d2019-02-28 12:06:45 -0700274 public static final @android.annotation.NonNull Parcelable.Creator<SpellCheckerSubtype> CREATOR
satok03b2ea12011-08-03 17:36:14 +0900275 = 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}