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