InputMethodSubtype.java revision 83e675f5ecf9f5615f3179ac102176faa3ae2596
1/* 2 * Copyright (C) 2010 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.inputmethod; 18 19import android.content.Context; 20import android.content.pm.ApplicationInfo; 21import android.os.Parcel; 22import android.os.Parcelable; 23import android.text.TextUtils; 24import android.util.Slog; 25 26import java.util.ArrayList; 27import java.util.Arrays; 28import java.util.HashMap; 29import java.util.HashSet; 30import java.util.IllegalFormatException; 31import java.util.List; 32import java.util.Locale; 33 34/** 35 * This class is used to specify meta information of a subtype contained in an input method editor 36 * (IME). Subtype can describe locale (e.g. en_US, fr_FR...) and mode (e.g. voice, keyboard...), 37 * and is used for IME switch and settings. The input method subtype allows the system to bring up 38 * the specified subtype of the designated IME directly. 39 * 40 * <p>It should be defined in an XML resource file of the input method with the 41 * <code><subtype></code> element. For more information, see the guide to 42 * <a href="{@docRoot}resources/articles/creating-input-method.html"> 43 * Creating an Input Method</a>.</p> 44 */ 45public final class InputMethodSubtype implements Parcelable { 46 private static final String TAG = InputMethodSubtype.class.getSimpleName(); 47 private static final String EXTRA_VALUE_PAIR_SEPARATOR = ","; 48 private static final String EXTRA_VALUE_KEY_VALUE_SEPARATOR = "="; 49 // TODO: remove this 50 private static final String EXTRA_KEY_UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME = 51 "UntranslatableReplacementStringInSubtypeName"; 52 53 private final boolean mIsAuxiliary; 54 private final boolean mOverridesImplicitlyEnabledSubtype; 55 private final int mSubtypeHashCode; 56 private final int mSubtypeIconResId; 57 private final int mSubtypeNameResId; 58 private final String mSubtypeLocale; 59 private final String mSubtypeMode; 60 private final String mSubtypeExtraValue; 61 private HashMap<String, String> mExtraValueHashMapCache; 62 63 /** 64 * Constructor. 65 * @param nameId Resource ID of the subtype name string. The string resource may have exactly 66 * one %s in it. If there is, the %s part will be replaced with the locale's display name by 67 * the formatter. Please refer to {@link #getDisplayName} for details. 68 * @param iconId Resource ID of the subtype icon drawable. 69 * @param locale The locale supported by the subtype 70 * @param mode The mode supported by the subtype 71 * @param extraValue The extra value of the subtype. This string is free-form, but the API 72 * supplies tools to deal with a key-value comma-separated list; see 73 * {@link #containsExtraValueKey} and {@link #getExtraValueOf}. 74 * @param isAuxiliary true when this subtype is auxiliary, false otherwise. An auxiliary 75 * subtype will not be shown in the list of enabled IMEs for choosing the current IME in 76 * the Settings even when this subtype is enabled. Please note that this subtype will still 77 * be shown in the list of IMEs in the IME switcher to allow the user to tentatively switch 78 * to this subtype while an IME is shown. The framework will never switch the current IME to 79 * this subtype by {@link android.view.inputmethod.InputMethodManager#switchToLastInputMethod}. 80 * The intent of having this flag is to allow for IMEs that are invoked in a one-shot way as 81 * auxiliary input mode, and return to the previous IME once it is finished (e.g. voice input). 82 * @hide 83 */ 84 public InputMethodSubtype(int nameId, int iconId, String locale, String mode, String extraValue, 85 boolean isAuxiliary) { 86 this(nameId, iconId, locale, mode, extraValue, isAuxiliary, false); 87 } 88 89 /** 90 * Constructor. 91 * @param nameId Resource ID of the subtype name string. The string resource may have exactly 92 * one %s in it. If there is, the %s part will be replaced with the locale's display name by 93 * the formatter. Please refer to {@link #getDisplayName} for details. 94 * @param iconId Resource ID of the subtype icon drawable. 95 * @param locale The locale supported by the subtype 96 * @param mode The mode supported by the subtype 97 * @param extraValue The extra value of the subtype. This string is free-form, but the API 98 * supplies tools to deal with a key-value comma-separated list; see 99 * {@link #containsExtraValueKey} and {@link #getExtraValueOf}. 100 * @param isAuxiliary true when this subtype is auxiliary, false otherwise. An auxiliary 101 * subtype will not be shown in the list of enabled IMEs for choosing the current IME in 102 * the Settings even when this subtype is enabled. Please note that this subtype will still 103 * be shown in the list of IMEs in the IME switcher to allow the user to tentatively switch 104 * to this subtype while an IME is shown. The framework will never switch the current IME to 105 * this subtype by {@link android.view.inputmethod.InputMethodManager#switchToLastInputMethod}. 106 * The intent of having this flag is to allow for IMEs that are invoked in a one-shot way as 107 * auxiliary input mode, and return to the previous IME once it is finished (e.g. voice input). 108 * @param overridesImplicitlyEnabledSubtype true when this subtype should be enabled by default 109 * if no other subtypes in the IME are enabled explicitly. Note that a subtype with this 110 * parameter being true will not be shown in the list of subtypes in each IME's subtype enabler. 111 * Having an "automatic" subtype is an example use of this flag. 112 */ 113 public InputMethodSubtype(int nameId, int iconId, String locale, String mode, String extraValue, 114 boolean isAuxiliary, boolean overridesImplicitlyEnabledSubtype) { 115 mSubtypeNameResId = nameId; 116 mSubtypeIconResId = iconId; 117 mSubtypeLocale = locale != null ? locale : ""; 118 mSubtypeMode = mode != null ? mode : ""; 119 mSubtypeExtraValue = extraValue != null ? extraValue : ""; 120 mIsAuxiliary = isAuxiliary; 121 mOverridesImplicitlyEnabledSubtype = overridesImplicitlyEnabledSubtype; 122 mSubtypeHashCode = hashCodeInternal(mSubtypeLocale, mSubtypeMode, mSubtypeExtraValue, 123 mIsAuxiliary, mOverridesImplicitlyEnabledSubtype); 124 } 125 126 InputMethodSubtype(Parcel source) { 127 String s; 128 mSubtypeNameResId = source.readInt(); 129 mSubtypeIconResId = source.readInt(); 130 s = source.readString(); 131 mSubtypeLocale = s != null ? s : ""; 132 s = source.readString(); 133 mSubtypeMode = s != null ? s : ""; 134 s = source.readString(); 135 mSubtypeExtraValue = s != null ? s : ""; 136 mIsAuxiliary = (source.readInt() == 1); 137 mOverridesImplicitlyEnabledSubtype = (source.readInt() == 1); 138 mSubtypeHashCode = hashCodeInternal(mSubtypeLocale, mSubtypeMode, mSubtypeExtraValue, 139 mIsAuxiliary, mOverridesImplicitlyEnabledSubtype); 140 } 141 142 /** 143 * @return Resource ID of the subtype name string. 144 */ 145 public int getNameResId() { 146 return mSubtypeNameResId; 147 } 148 149 /** 150 * @return Resource ID of the subtype icon drawable. 151 */ 152 public int getIconResId() { 153 return mSubtypeIconResId; 154 } 155 156 /** 157 * @return The locale of the subtype. This method returns the "locale" string parameter passed 158 * to the constructor. 159 */ 160 public String getLocale() { 161 return mSubtypeLocale; 162 } 163 164 /** 165 * @return The mode of the subtype. 166 */ 167 public String getMode() { 168 return mSubtypeMode; 169 } 170 171 /** 172 * @return The extra value of the subtype. 173 */ 174 public String getExtraValue() { 175 return mSubtypeExtraValue; 176 } 177 178 /** 179 * @return true if this subtype is auxiliary, false otherwise. An auxiliary subtype will not be 180 * shown in the list of enabled IMEs for choosing the current IME in the Settings even when this 181 * subtype is enabled. Please note that this subtype will still be shown in the list of IMEs in 182 * the IME switcher to allow the user to tentatively switch to this subtype while an IME is 183 * shown. The framework will never switch the current IME to this subtype by 184 * {@link android.view.inputmethod.InputMethodManager#switchToLastInputMethod}. 185 * The intent of having this flag is to allow for IMEs that are invoked in a one-shot way as 186 * auxiliary input mode, and return to the previous IME once it is finished (e.g. voice input). 187 */ 188 public boolean isAuxiliary() { 189 return mIsAuxiliary; 190 } 191 192 /** 193 * @return true when this subtype will be enabled by default if no other subtypes in the IME 194 * are enabled explicitly, false otherwise. Note that a subtype with this method returning true 195 * will not be shown in the list of subtypes in each IME's subtype enabler. Having an 196 * "automatic" subtype is an example use of this flag. 197 */ 198 public boolean overridesImplicitlyEnabledSubtype() { 199 return mOverridesImplicitlyEnabledSubtype; 200 } 201 202 /** 203 * @param context Context will be used for getting Locale and PackageManager. 204 * @param packageName The package name of the IME 205 * @param appInfo The application info of the IME 206 * @return a display name for this subtype. The string resource of the label (mSubtypeNameResId) 207 * may have exactly one %s in it. If there is, the %s part will be replaced with the locale's 208 * display name by the formatter. If there is not, this method returns the string specified by 209 * mSubtypeNameResId. If mSubtypeNameResId is not specified (== 0), it's up to the framework to 210 * generate an appropriate display name. 211 */ 212 public CharSequence getDisplayName( 213 Context context, String packageName, ApplicationInfo appInfo) { 214 final Locale locale = constructLocaleFromString(mSubtypeLocale); 215 final String localeStr = locale != null ? locale.getDisplayName() : mSubtypeLocale; 216 if (mSubtypeNameResId == 0) { 217 return localeStr; 218 } 219 final CharSequence subtypeName = context.getPackageManager().getText( 220 packageName, mSubtypeNameResId, appInfo); 221 if (!TextUtils.isEmpty(subtypeName)) { 222 final String replacementString = 223 containsExtraValueKey(EXTRA_KEY_UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME) 224 ? getExtraValueOf(EXTRA_KEY_UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME) 225 : localeStr; 226 try { 227 return String.format( 228 subtypeName.toString(), replacementString != null ? replacementString : ""); 229 } catch (IllegalFormatException e) { 230 Slog.w(TAG, "Found illegal format in subtype name("+ subtypeName + "): " + e); 231 return ""; 232 } 233 } else { 234 return localeStr; 235 } 236 } 237 238 private HashMap<String, String> getExtraValueHashMap() { 239 if (mExtraValueHashMapCache == null) { 240 mExtraValueHashMapCache = new HashMap<String, String>(); 241 final String[] pairs = mSubtypeExtraValue.split(EXTRA_VALUE_PAIR_SEPARATOR); 242 final int N = pairs.length; 243 for (int i = 0; i < N; ++i) { 244 final String[] pair = pairs[i].split(EXTRA_VALUE_KEY_VALUE_SEPARATOR); 245 if (pair.length == 1) { 246 mExtraValueHashMapCache.put(pair[0], null); 247 } else if (pair.length > 1) { 248 if (pair.length > 2) { 249 Slog.w(TAG, "ExtraValue has two or more '='s"); 250 } 251 mExtraValueHashMapCache.put(pair[0], pair[1]); 252 } 253 } 254 } 255 return mExtraValueHashMapCache; 256 } 257 258 /** 259 * The string of ExtraValue in subtype should be defined as follows: 260 * example: key0,key1=value1,key2,key3,key4=value4 261 * @param key The key of extra value 262 * @return The subtype contains specified the extra value 263 */ 264 public boolean containsExtraValueKey(String key) { 265 return getExtraValueHashMap().containsKey(key); 266 } 267 268 /** 269 * The string of ExtraValue in subtype should be defined as follows: 270 * example: key0,key1=value1,key2,key3,key4=value4 271 * @param key The key of extra value 272 * @return The value of the specified key 273 */ 274 public String getExtraValueOf(String key) { 275 return getExtraValueHashMap().get(key); 276 } 277 278 @Override 279 public int hashCode() { 280 return mSubtypeHashCode; 281 } 282 283 @Override 284 public boolean equals(Object o) { 285 if (o instanceof InputMethodSubtype) { 286 InputMethodSubtype subtype = (InputMethodSubtype) o; 287 return (subtype.hashCode() == hashCode()) 288 && (subtype.getNameResId() == getNameResId()) 289 && (subtype.getMode().equals(getMode())) 290 && (subtype.getIconResId() == getIconResId()) 291 && (subtype.getLocale().equals(getLocale())) 292 && (subtype.getExtraValue().equals(getExtraValue())) 293 && (subtype.isAuxiliary() == isAuxiliary()); 294 } 295 return false; 296 } 297 298 @Override 299 public int describeContents() { 300 return 0; 301 } 302 303 @Override 304 public void writeToParcel(Parcel dest, int parcelableFlags) { 305 dest.writeInt(mSubtypeNameResId); 306 dest.writeInt(mSubtypeIconResId); 307 dest.writeString(mSubtypeLocale); 308 dest.writeString(mSubtypeMode); 309 dest.writeString(mSubtypeExtraValue); 310 dest.writeInt(mIsAuxiliary ? 1 : 0); 311 dest.writeInt(mOverridesImplicitlyEnabledSubtype ? 1 : 0); 312 } 313 314 public static final Parcelable.Creator<InputMethodSubtype> CREATOR 315 = new Parcelable.Creator<InputMethodSubtype>() { 316 @Override 317 public InputMethodSubtype createFromParcel(Parcel source) { 318 return new InputMethodSubtype(source); 319 } 320 321 @Override 322 public InputMethodSubtype[] newArray(int size) { 323 return new InputMethodSubtype[size]; 324 } 325 }; 326 327 private static Locale constructLocaleFromString(String localeStr) { 328 if (TextUtils.isEmpty(localeStr)) 329 return null; 330 String[] localeParams = localeStr.split("_", 3); 331 // The length of localeStr is guaranteed to always return a 1 <= value <= 3 332 // because localeStr is not empty. 333 if (localeParams.length == 1) { 334 return new Locale(localeParams[0]); 335 } else if (localeParams.length == 2) { 336 return new Locale(localeParams[0], localeParams[1]); 337 } else if (localeParams.length == 3) { 338 return new Locale(localeParams[0], localeParams[1], localeParams[2]); 339 } 340 return null; 341 } 342 343 private static int hashCodeInternal(String locale, String mode, String extraValue, 344 boolean isAuxiliary, boolean overridesImplicitlyEnabledSubtype) { 345 return Arrays.hashCode(new Object[] {locale, mode, extraValue, isAuxiliary, 346 overridesImplicitlyEnabledSubtype}); 347 } 348 349 /** 350 * Sort the list of InputMethodSubtype 351 * @param context Context will be used for getting localized strings from IME 352 * @param flags Flags for the sort order 353 * @param imi InputMethodInfo of which subtypes are subject to be sorted 354 * @param subtypeList List of InputMethodSubtype which will be sorted 355 * @return Sorted list of subtypes 356 * @hide 357 */ 358 public static List<InputMethodSubtype> sort(Context context, int flags, InputMethodInfo imi, 359 List<InputMethodSubtype> subtypeList) { 360 if (imi == null) return subtypeList; 361 final HashSet<InputMethodSubtype> inputSubtypesSet = new HashSet<InputMethodSubtype>( 362 subtypeList); 363 final ArrayList<InputMethodSubtype> sortedList = new ArrayList<InputMethodSubtype>(); 364 int N = imi.getSubtypeCount(); 365 for (int i = 0; i < N; ++i) { 366 InputMethodSubtype subtype = imi.getSubtypeAt(i); 367 if (inputSubtypesSet.contains(subtype)) { 368 sortedList.add(subtype); 369 inputSubtypesSet.remove(subtype); 370 } 371 } 372 // If subtypes in inputSubtypesSet remain, that means these subtypes are not 373 // contained in imi, so the remaining subtypes will be appended. 374 for (InputMethodSubtype subtype: inputSubtypesSet) { 375 sortedList.add(subtype); 376 } 377 return sortedList; 378 } 379} 380