Home | History | Annotate | Download | only in latin
      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 String[] sPredefinedKeyboardLayoutSet;
     46     // Keyboard layout to its display name map.
     47     private static final HashMap<String, String> sKeyboardLayoutToDisplayNameMap =
     48             CollectionUtils.newHashMap();
     49     // Keyboard layout to subtype name resource id map.
     50     private static final HashMap<String, Integer> sKeyboardLayoutToNameIdsMap =
     51             CollectionUtils.newHashMap();
     52     // Exceptional locale to subtype name resource id map.
     53     private static final HashMap<String, Integer> sExceptionalLocaleToWithLayoutNameIdsMap =
     54             CollectionUtils.newHashMap();
     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             CollectionUtils.newHashMap();
     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             CollectionUtils.newHashMap();
     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