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