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 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 String[] sPredefinedKeyboardLayoutSet; 46 // Keyboard layout to its display name map. 47 private static final HashMap<String, String> sKeyboardLayoutToDisplayNameMap = 48 new HashMap<String, String>(); 49 // Keyboard layout to subtype name resource id map. 50 private static final HashMap<String, Integer> sKeyboardLayoutToNameIdsMap = 51 new HashMap<String, Integer>(); 52 // Exceptional locale to subtype name resource id map. 53 private static final HashMap<String, Integer> sExceptionalLocaleToWithLayoutNameIdsMap = 54 new HashMap<String, Integer>(); 55 private static final String SUBTYPE_NAME_RESOURCE_GENERIC_PREFIX = 56 "string/subtype_generic_"; 57 private static final String SUBTYPE_NAME_RESOURCE_WITH_LAYOUT_PREFIX = 58 "string/subtype_with_layout_"; 59 private static final String SUBTYPE_NAME_RESOURCE_NO_LANGUAGE_PREFIX = 60 "string/subtype_no_language_"; 61 // Exceptional locales to display name map. 62 private static final HashMap<String, String> sExceptionalDisplayNamesMap = 63 new HashMap<String, String>(); 64 // Keyboard layout set name for the subtypes that don't have a keyboardLayoutSet extra value. 65 // This is for compatibility to keep the same subtype ids as pre-JellyBean. 66 private static final HashMap<String,String> sLocaleAndExtraValueToKeyboardLayoutSetMap = 67 new HashMap<String,String>(); 68 69 private SubtypeLocale() { 70 // Intentional empty constructor for utility class. 71 } 72 73 // Note that this initialization method can be called multiple times. 74 public static synchronized void init(Context context) { 75 if (sInitialized) return; 76 77 final Resources res = context.getResources(); 78 79 final String[] predefinedLayoutSet = res.getStringArray(R.array.predefined_layouts); 80 sPredefinedKeyboardLayoutSet = predefinedLayoutSet; 81 final String[] layoutDisplayNames = res.getStringArray( 82 R.array.predefined_layout_display_names); 83 for (int i = 0; i < predefinedLayoutSet.length; i++) { 84 final String layoutName = predefinedLayoutSet[i]; 85 sKeyboardLayoutToDisplayNameMap.put(layoutName, layoutDisplayNames[i]); 86 final String resourceName = SUBTYPE_NAME_RESOURCE_GENERIC_PREFIX + layoutName; 87 final int resId = res.getIdentifier(resourceName, null, RESOURCE_PACKAGE_NAME); 88 sKeyboardLayoutToNameIdsMap.put(layoutName, resId); 89 // Register subtype name resource id of "No language" with key "zz_<layout>" 90 final String noLanguageResName = SUBTYPE_NAME_RESOURCE_NO_LANGUAGE_PREFIX + layoutName; 91 final int noLanguageResId = res.getIdentifier( 92 noLanguageResName, null, RESOURCE_PACKAGE_NAME); 93 final String key = getNoLanguageLayoutKey(layoutName); 94 sKeyboardLayoutToNameIdsMap.put(key, noLanguageResId); 95 } 96 97 final String[] exceptionalLocales = res.getStringArray( 98 R.array.subtype_locale_exception_keys); 99 final String[] exceptionalDisplayNames = res.getStringArray( 100 R.array.subtype_locale_exception_values); 101 for (int i = 0; i < exceptionalLocales.length; i++) { 102 final String localeString = exceptionalLocales[i]; 103 sExceptionalDisplayNamesMap.put(localeString, exceptionalDisplayNames[i]); 104 final String resourceName = SUBTYPE_NAME_RESOURCE_WITH_LAYOUT_PREFIX + localeString; 105 final int resId = res.getIdentifier(resourceName, null, RESOURCE_PACKAGE_NAME); 106 sExceptionalLocaleToWithLayoutNameIdsMap.put(localeString, resId); 107 } 108 109 final String[] keyboardLayoutSetMap = res.getStringArray( 110 R.array.locale_and_extra_value_to_keyboard_layout_set_map); 111 for (int i = 0; i < keyboardLayoutSetMap.length; i += 2) { 112 final String key = keyboardLayoutSetMap[i]; 113 final String keyboardLayoutSet = keyboardLayoutSetMap[i + 1]; 114 sLocaleAndExtraValueToKeyboardLayoutSetMap.put(key, keyboardLayoutSet); 115 } 116 117 sInitialized = true; 118 } 119 120 public static String[] getPredefinedKeyboardLayoutSet() { 121 return sPredefinedKeyboardLayoutSet; 122 } 123 124 public static boolean isExceptionalLocale(String localeString) { 125 return sExceptionalLocaleToWithLayoutNameIdsMap.containsKey(localeString); 126 } 127 128 private static final String getNoLanguageLayoutKey(String keyboardLayoutName) { 129 return NO_LANGUAGE + "_" + keyboardLayoutName; 130 } 131 132 public static int getSubtypeNameId(String localeString, String keyboardLayoutName) { 133 if (Build.VERSION.SDK_INT >= /* JELLY_BEAN */ 15 && isExceptionalLocale(localeString)) { 134 return sExceptionalLocaleToWithLayoutNameIdsMap.get(localeString); 135 } 136 final String key = localeString.equals(NO_LANGUAGE) 137 ? getNoLanguageLayoutKey(keyboardLayoutName) 138 : keyboardLayoutName; 139 final Integer nameId = sKeyboardLayoutToNameIdsMap.get(key); 140 return nameId == null ? UNKNOWN_KEYBOARD_LAYOUT : nameId; 141 } 142 143 public static String getSubtypeLocaleDisplayName(String localeString) { 144 final String exceptionalValue = sExceptionalDisplayNamesMap.get(localeString); 145 if (exceptionalValue != null) { 146 return exceptionalValue; 147 } 148 final Locale locale = LocaleUtils.constructLocaleFromString(localeString); 149 return StringUtils.toTitleCase(locale.getDisplayName(locale), locale); 150 } 151 152 // InputMethodSubtype's display name in its locale. 153 // isAdditionalSubtype (T=true, F=false) 154 // locale layout | display name 155 // ------ ------ - ---------------------- 156 // en_US qwerty F English (US) exception 157 // en_GB qwerty F English (UK) exception 158 // fr azerty F Franais 159 // fr_CA qwerty F Franais (Canada) 160 // de qwertz F Deutsch 161 // zz qwerty F No language (QWERTY) in system locale 162 // fr qwertz T Franais (QWERTZ) 163 // de qwerty T Deutsch (QWERTY) 164 // en_US azerty T English (US) (AZERTY) 165 // zz azerty T No language (AZERTY) in system locale 166 167 public static String getSubtypeDisplayName(final InputMethodSubtype subtype, Resources res) { 168 final String replacementString = (Build.VERSION.SDK_INT >= /* JELLY_BEAN */ 15 169 && subtype.containsExtraValueKey(UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME)) 170 ? subtype.getExtraValueOf(UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME) 171 : getSubtypeLocaleDisplayName(subtype.getLocale()); 172 final int nameResId = subtype.getNameResId(); 173 final RunInLocale<String> getSubtypeName = new RunInLocale<String>() { 174 @Override 175 protected String job(Resources res) { 176 try { 177 return res.getString(nameResId, replacementString); 178 } catch (Resources.NotFoundException e) { 179 // TODO: Remove this catch when InputMethodManager.getCurrentInputMethodSubtype 180 // is fixed. 181 Log.w(TAG, "Unknown subtype: mode=" + subtype.getMode() 182 + " locale=" + subtype.getLocale() 183 + " extra=" + subtype.getExtraValue() 184 + "\n" + Utils.getStackTrace()); 185 return ""; 186 } 187 } 188 }; 189 final Locale locale = isNoLanguage(subtype) 190 ? res.getConfiguration().locale : getSubtypeLocale(subtype); 191 return getSubtypeName.runInLocale(res, locale); 192 } 193 194 public static boolean isNoLanguage(InputMethodSubtype subtype) { 195 final String localeString = subtype.getLocale(); 196 return localeString.equals(NO_LANGUAGE); 197 } 198 199 public static Locale getSubtypeLocale(InputMethodSubtype subtype) { 200 final String localeString = subtype.getLocale(); 201 return LocaleUtils.constructLocaleFromString(localeString); 202 } 203 204 public static String getKeyboardLayoutSetDisplayName(InputMethodSubtype subtype) { 205 final String layoutName = getKeyboardLayoutSetName(subtype); 206 return getKeyboardLayoutSetDisplayName(layoutName); 207 } 208 209 public static String getKeyboardLayoutSetDisplayName(String layoutName) { 210 return sKeyboardLayoutToDisplayNameMap.get(layoutName); 211 } 212 213 public static String getKeyboardLayoutSetName(InputMethodSubtype subtype) { 214 String keyboardLayoutSet = subtype.getExtraValueOf(KEYBOARD_LAYOUT_SET); 215 if (keyboardLayoutSet == null) { 216 // This subtype doesn't have a keyboardLayoutSet extra value, so lookup its keyboard 217 // layout set in sLocaleAndExtraValueToKeyboardLayoutSetMap to keep it compatible with 218 // pre-JellyBean. 219 final String key = subtype.getLocale() + ":" + subtype.getExtraValue(); 220 keyboardLayoutSet = sLocaleAndExtraValueToKeyboardLayoutSetMap.get(key); 221 } 222 // TODO: Remove this null check when InputMethodManager.getCurrentInputMethodSubtype is 223 // fixed. 224 if (keyboardLayoutSet == null) { 225 android.util.Log.w(TAG, "KeyboardLayoutSet not found, use QWERTY: " + 226 "locale=" + subtype.getLocale() + " extraValue=" + subtype.getExtraValue()); 227 return QWERTY; 228 } 229 return keyboardLayoutSet; 230 } 231 } 232