• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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