SubtypeLocaleUtils.java revision 2202d47386598815c7bda939f36759e7b923cc61
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 com.android.inputmethod.latin.utils; 18 19import static com.android.inputmethod.latin.Constants.Subtype.ExtraValue.KEYBOARD_LAYOUT_SET; 20import static com.android.inputmethod.latin.Constants.Subtype.ExtraValue.UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME; 21 22import android.content.Context; 23import android.content.res.Resources; 24import android.os.Build; 25import android.util.Log; 26import android.view.inputmethod.InputMethodSubtype; 27 28import com.android.inputmethod.latin.DictionaryFactory; 29import com.android.inputmethod.latin.R; 30 31import java.util.Arrays; 32import java.util.HashMap; 33import java.util.Locale; 34 35public final class SubtypeLocaleUtils { 36 static final String TAG = SubtypeLocaleUtils.class.getSimpleName(); 37 // This class must be located in the same package as LatinIME.java. 38 private static final String RESOURCE_PACKAGE_NAME = 39 DictionaryFactory.class.getPackage().getName(); 40 41 // Special language code to represent "no language". 42 public static final String NO_LANGUAGE = "zz"; 43 public static final String QWERTY = "qwerty"; 44 public static final String EMOJI = "emoji"; 45 public static final int UNKNOWN_KEYBOARD_LAYOUT = R.string.subtype_generic; 46 47 private static boolean sInitialized = false; 48 private static Resources sResources; 49 private static String[] sPredefinedKeyboardLayoutSet; 50 // Keyboard layout to its display name map. 51 private static final HashMap<String, String> sKeyboardLayoutToDisplayNameMap = 52 CollectionUtils.newHashMap(); 53 // Keyboard layout to subtype name resource id map. 54 private static final HashMap<String, Integer> sKeyboardLayoutToNameIdsMap = 55 CollectionUtils.newHashMap(); 56 // Exceptional locale to subtype name resource id map. 57 private static final HashMap<String, Integer> sExceptionalLocaleToNameIdsMap = 58 CollectionUtils.newHashMap(); 59 // Exceptional locale to subtype name with layout resource id map. 60 private static final HashMap<String, Integer> sExceptionalLocaleToWithLayoutNameIdsMap = 61 CollectionUtils.newHashMap(); 62 private static final String SUBTYPE_NAME_RESOURCE_PREFIX = 63 "string/subtype_"; 64 private static final String SUBTYPE_NAME_RESOURCE_GENERIC_PREFIX = 65 "string/subtype_generic_"; 66 private static final String SUBTYPE_NAME_RESOURCE_WITH_LAYOUT_PREFIX = 67 "string/subtype_with_layout_"; 68 private static final String SUBTYPE_NAME_RESOURCE_NO_LANGUAGE_PREFIX = 69 "string/subtype_no_language_"; 70 // Keyboard layout set name for the subtypes that don't have a keyboardLayoutSet extra value. 71 // This is for compatibility to keep the same subtype ids as pre-JellyBean. 72 private static final HashMap<String, String> sLocaleAndExtraValueToKeyboardLayoutSetMap = 73 CollectionUtils.newHashMap(); 74 75 private SubtypeLocaleUtils() { 76 // Intentional empty constructor for utility class. 77 } 78 79 // Note that this initialization method can be called multiple times. 80 public static synchronized void init(final Context context) { 81 if (sInitialized) return; 82 83 final Resources res = context.getResources(); 84 sResources = res; 85 86 final String[] predefinedLayoutSet = res.getStringArray(R.array.predefined_layouts); 87 sPredefinedKeyboardLayoutSet = predefinedLayoutSet; 88 final String[] layoutDisplayNames = res.getStringArray( 89 R.array.predefined_layout_display_names); 90 for (int i = 0; i < predefinedLayoutSet.length; i++) { 91 final String layoutName = predefinedLayoutSet[i]; 92 sKeyboardLayoutToDisplayNameMap.put(layoutName, layoutDisplayNames[i]); 93 final String resourceName = SUBTYPE_NAME_RESOURCE_GENERIC_PREFIX + layoutName; 94 final int resId = res.getIdentifier(resourceName, null, RESOURCE_PACKAGE_NAME); 95 sKeyboardLayoutToNameIdsMap.put(layoutName, resId); 96 // Register subtype name resource id of "No language" with key "zz_<layout>" 97 final String noLanguageResName = SUBTYPE_NAME_RESOURCE_NO_LANGUAGE_PREFIX + layoutName; 98 final int noLanguageResId = res.getIdentifier( 99 noLanguageResName, null, RESOURCE_PACKAGE_NAME); 100 final String key = getNoLanguageLayoutKey(layoutName); 101 sKeyboardLayoutToNameIdsMap.put(key, noLanguageResId); 102 } 103 104 final String[] exceptionalLocales = res.getStringArray( 105 R.array.subtype_locale_exception_keys); 106 for (int i = 0; i < exceptionalLocales.length; i++) { 107 final String localeString = exceptionalLocales[i]; 108 final String resourceName = SUBTYPE_NAME_RESOURCE_PREFIX + localeString; 109 final int resId = res.getIdentifier(resourceName, null, RESOURCE_PACKAGE_NAME); 110 sExceptionalLocaleToNameIdsMap.put(localeString, resId); 111 final String resourceNameWithLayout = 112 SUBTYPE_NAME_RESOURCE_WITH_LAYOUT_PREFIX + localeString; 113 final int resIdWithLayout = res.getIdentifier( 114 resourceNameWithLayout, null, RESOURCE_PACKAGE_NAME); 115 sExceptionalLocaleToWithLayoutNameIdsMap.put(localeString, resIdWithLayout); 116 } 117 118 final String[] keyboardLayoutSetMap = res.getStringArray( 119 R.array.locale_and_extra_value_to_keyboard_layout_set_map); 120 for (int i = 0; i + 1 < keyboardLayoutSetMap.length; i += 2) { 121 final String key = keyboardLayoutSetMap[i]; 122 final String keyboardLayoutSet = keyboardLayoutSetMap[i + 1]; 123 sLocaleAndExtraValueToKeyboardLayoutSetMap.put(key, keyboardLayoutSet); 124 } 125 126 sInitialized = true; 127 } 128 129 public static String[] getPredefinedKeyboardLayoutSet() { 130 return sPredefinedKeyboardLayoutSet; 131 } 132 133 public static boolean isExceptionalLocale(final String localeString) { 134 return sExceptionalLocaleToNameIdsMap.containsKey(localeString); 135 } 136 137 private static final String getNoLanguageLayoutKey(final String keyboardLayoutName) { 138 return NO_LANGUAGE + "_" + keyboardLayoutName; 139 } 140 141 public static int getSubtypeNameId(final String localeString, final String keyboardLayoutName) { 142 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN 143 && isExceptionalLocale(localeString)) { 144 return sExceptionalLocaleToWithLayoutNameIdsMap.get(localeString); 145 } 146 final String key = NO_LANGUAGE.equals(localeString) 147 ? getNoLanguageLayoutKey(keyboardLayoutName) 148 : keyboardLayoutName; 149 final Integer nameId = sKeyboardLayoutToNameIdsMap.get(key); 150 return nameId == null ? UNKNOWN_KEYBOARD_LAYOUT : nameId; 151 } 152 153 private static Locale getDisplayLocaleOfSubtypeLocale(final String localeString) { 154 if (NO_LANGUAGE.equals(localeString)) { 155 return sResources.getConfiguration().locale; 156 } 157 return LocaleUtils.constructLocaleFromString(localeString); 158 } 159 160 public static String getSubtypeLocaleDisplayNameInSystemLocale(final String localeString) { 161 final Locale displayLocale = sResources.getConfiguration().locale; 162 return getSubtypeLocaleDisplayNameInternal(localeString, displayLocale); 163 } 164 165 public static String getSubtypeLocaleDisplayName(final String localeString) { 166 final Locale displayLocale = getDisplayLocaleOfSubtypeLocale(localeString); 167 return getSubtypeLocaleDisplayNameInternal(localeString, displayLocale); 168 } 169 170 private static String getSubtypeLocaleDisplayNameInternal(final String localeString, 171 final Locale displayLocale) { 172 final Integer exceptionalNameResId = sExceptionalLocaleToNameIdsMap.get(localeString); 173 final String displayName; 174 if (exceptionalNameResId != null) { 175 final RunInLocale<String> getExceptionalName = new RunInLocale<String>() { 176 @Override 177 protected String job(final Resources res) { 178 return res.getString(exceptionalNameResId); 179 } 180 }; 181 displayName = getExceptionalName.runInLocale(sResources, displayLocale); 182 } else if (NO_LANGUAGE.equals(localeString)) { 183 // No language subtype should be displayed in system locale. 184 return sResources.getString(R.string.subtype_no_language); 185 } else { 186 final Locale locale = LocaleUtils.constructLocaleFromString(localeString); 187 displayName = locale.getDisplayName(displayLocale); 188 } 189 return StringUtils.capitalizeFirstCodePoint(displayName, displayLocale); 190 } 191 192 // InputMethodSubtype's display name in its locale. 193 // isAdditionalSubtype (T=true, F=false) 194 // locale layout | display name 195 // ------ ------- - ---------------------- 196 // en_US qwerty F English (US) exception 197 // en_GB qwerty F English (UK) exception 198 // es_US spanish F Español (EE.UU.) exception 199 // fr azerty F Français 200 // fr_CA qwerty F Français (Canada) 201 // fr_CH swiss F Français (Suisse) 202 // de qwertz F Deutsch 203 // de_CH swiss T Deutsch (Schweiz) 204 // zz qwerty F Alphabet (QWERTY) in system locale 205 // fr qwertz T Français (QWERTZ) 206 // de qwerty T Deutsch (QWERTY) 207 // en_US azerty T English (US) (AZERTY) exception 208 // zz azerty T Alphabet (AZERTY) in system locale 209 210 private static String getReplacementString(final InputMethodSubtype subtype, 211 final Locale displayLocale) { 212 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN 213 && subtype.containsExtraValueKey(UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME)) { 214 return subtype.getExtraValueOf(UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME); 215 } else { 216 return getSubtypeLocaleDisplayNameInternal(subtype.getLocale(), displayLocale); 217 } 218 } 219 220 public static String getSubtypeDisplayNameInSystemLocale(final InputMethodSubtype subtype) { 221 final Locale displayLocale = sResources.getConfiguration().locale; 222 return getSubtypeDisplayNameInternal(subtype, displayLocale); 223 } 224 225 public static String getSubtypeNameForLogging(final InputMethodSubtype subtype) { 226 if (subtype == null) { 227 return "<null subtype>"; 228 } 229 return getSubtypeLocale(subtype) + "/" + getKeyboardLayoutSetName(subtype); 230 } 231 232 private static String getSubtypeDisplayNameInternal(final InputMethodSubtype subtype, 233 final Locale displayLocale) { 234 final String replacementString = getReplacementString(subtype, displayLocale); 235 final int nameResId = subtype.getNameResId(); 236 final RunInLocale<String> getSubtypeName = new RunInLocale<String>() { 237 @Override 238 protected String job(final Resources res) { 239 try { 240 return res.getString(nameResId, replacementString); 241 } catch (Resources.NotFoundException e) { 242 // TODO: Remove this catch when InputMethodManager.getCurrentInputMethodSubtype 243 // is fixed. 244 Log.w(TAG, "Unknown subtype: mode=" + subtype.getMode() 245 + " nameResId=" + subtype.getNameResId() 246 + " locale=" + subtype.getLocale() 247 + " extra=" + subtype.getExtraValue() 248 + "\n" + DebugLogUtils.getStackTrace()); 249 return ""; 250 } 251 } 252 }; 253 return StringUtils.capitalizeFirstCodePoint( 254 getSubtypeName.runInLocale(sResources, displayLocale), displayLocale); 255 } 256 257 public static boolean isNoLanguage(final InputMethodSubtype subtype) { 258 final String localeString = subtype.getLocale(); 259 return NO_LANGUAGE.equals(localeString); 260 } 261 262 public static Locale getSubtypeLocale(final InputMethodSubtype subtype) { 263 final String localeString = subtype.getLocale(); 264 return LocaleUtils.constructLocaleFromString(localeString); 265 } 266 267 public static String getKeyboardLayoutSetDisplayName(final InputMethodSubtype subtype) { 268 final String layoutName = getKeyboardLayoutSetName(subtype); 269 return getKeyboardLayoutSetDisplayName(layoutName); 270 } 271 272 public static String getKeyboardLayoutSetDisplayName(final String layoutName) { 273 return sKeyboardLayoutToDisplayNameMap.get(layoutName); 274 } 275 276 public static String getKeyboardLayoutSetName(final InputMethodSubtype subtype) { 277 String keyboardLayoutSet = subtype.getExtraValueOf(KEYBOARD_LAYOUT_SET); 278 if (keyboardLayoutSet == null) { 279 // This subtype doesn't have a keyboardLayoutSet extra value, so lookup its keyboard 280 // layout set in sLocaleAndExtraValueToKeyboardLayoutSetMap to keep it compatible with 281 // pre-JellyBean. 282 final String key = subtype.getLocale() + ":" + subtype.getExtraValue(); 283 keyboardLayoutSet = sLocaleAndExtraValueToKeyboardLayoutSetMap.get(key); 284 } 285 // TODO: Remove this null check when InputMethodManager.getCurrentInputMethodSubtype is 286 // fixed. 287 if (keyboardLayoutSet == null) { 288 android.util.Log.w(TAG, "KeyboardLayoutSet not found, use QWERTY: " + 289 "locale=" + subtype.getLocale() + " extraValue=" + subtype.getExtraValue()); 290 return QWERTY; 291 } 292 return keyboardLayoutSet; 293 } 294 295 // InputMethodSubtype's display name for spacebar text in its locale. 296 // isAdditionalSubtype (T=true, F=false) 297 // locale layout | Middle Full 298 // ------ ------- - --------- ---------------------- 299 // en_US qwerty F English English (US) exception 300 // en_GB qwerty F English English (UK) exception 301 // es_US spanish F Español Español (EE.UU.) exception 302 // fr azerty F Français Français 303 // fr_CA qwerty F Français Français (Canada) 304 // fr_CH swiss F Français Français (Suisse) 305 // de qwertz F Deutsch Deutsch 306 // de_CH swiss T Deutsch Deutsch (Schweiz) 307 // zz qwerty F QWERTY QWERTY 308 // fr qwertz T Français Français 309 // de qwerty T Deutsch Deutsch 310 // en_US azerty T English English (US) 311 // zz azerty T AZERTY AZERTY 312 313 // Get InputMethodSubtype's full display name in its locale. 314 public static String getFullDisplayName(final InputMethodSubtype subtype) { 315 if (isNoLanguage(subtype)) { 316 return getKeyboardLayoutSetDisplayName(subtype); 317 } 318 return getSubtypeLocaleDisplayName(subtype.getLocale()); 319 } 320 321 // Get InputMethodSubtype's middle display name in its locale. 322 public static String getMiddleDisplayName(final InputMethodSubtype subtype) { 323 if (isNoLanguage(subtype)) { 324 return getKeyboardLayoutSetDisplayName(subtype); 325 } 326 final Locale locale = getSubtypeLocale(subtype); 327 return getSubtypeLocaleDisplayName(locale.getLanguage()); 328 } 329 330 // TODO: Get this information from the framework instead of maintaining here by ourselves. 331 // Sorted list of known Right-To-Left language codes. 332 private static final String[] SORTED_RTL_LANGUAGES = { 333 "ar", // Arabic 334 "fa", // Persian 335 "iw", // Hebrew 336 }; 337 static { 338 Arrays.sort(SORTED_RTL_LANGUAGES); 339 } 340 341 public static boolean isRtlLanguage(final Locale locale) { 342 final String language = locale.getLanguage(); 343 return Arrays.binarySearch(SORTED_RTL_LANGUAGES, language) >= 0; 344 } 345 346 public static boolean isRtlLanguage(final InputMethodSubtype subtype) { 347 return isRtlLanguage(getSubtypeLocale(subtype)); 348 } 349} 350