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