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 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 
     72     private SubtypeLocale() {
     73         // Intentional empty constructor for utility class.
     74     }
     75 
     76     // Note that this initialization method can be called multiple times.
     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 
    126     public static String[] getPredefinedKeyboardLayoutSet() {
    127         return sPredefinedKeyboardLayoutSet;
    128     }
    129 
    130     public static boolean isExceptionalLocale(final String localeString) {
    131         return sExceptionalLocaleToNameIdsMap.containsKey(localeString);
    132     }
    133 
    134     private static final String getNoLanguageLayoutKey(final String keyboardLayoutName) {
    135         return NO_LANGUAGE + "_" + keyboardLayoutName;
    136     }
    137 
    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 
    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 
    157     public static String getSubtypeLocaleDisplayNameInSystemLocale(final String localeString) {
    158         final Locale displayLocale = sResources.getConfiguration().locale;
    159         return getSubtypeLocaleDisplayNameInternal(localeString, displayLocale);
    160     }
    161 
    162     public static String getSubtypeLocaleDisplayName(final String localeString) {
    163         final Locale displayLocale = getDisplayLocaleOfSubtypeLocale(localeString);
    164         return getSubtypeLocaleDisplayNameInternal(localeString, displayLocale);
    165     }
    166 
    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  Espaol (EE.UU.)        exception
    196     //  fr    azerty  F  Franais
    197     //  fr_CA qwerty  F  Franais (Canada)
    198     //  de    qwertz  F  Deutsch
    199     //  zz    qwerty  F  No language (QWERTY)    in system locale
    200     //  fr    qwertz  T  Franais (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 
    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 
    215     public static String getSubtypeDisplayNameInSystemLocale(final InputMethodSubtype subtype) {
    216         final Locale displayLocale = sResources.getConfiguration().locale;
    217         return getSubtypeDisplayNameInternal(subtype, displayLocale);
    218     }
    219 
    220     public static String getSubtypeDisplayName(final InputMethodSubtype subtype) {
    221         final Locale displayLocale = getDisplayLocaleOfSubtypeLocale(subtype.getLocale());
    222         return getSubtypeDisplayNameInternal(subtype, displayLocale);
    223     }
    224 
    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 
    250     public static boolean isNoLanguage(final InputMethodSubtype subtype) {
    251         final String localeString = subtype.getLocale();
    252         return NO_LANGUAGE.equals(localeString);
    253     }
    254 
    255     public static Locale getSubtypeLocale(final InputMethodSubtype subtype) {
    256         final String localeString = subtype.getLocale();
    257         return LocaleUtils.constructLocaleFromString(localeString);
    258     }
    259 
    260     public static String getKeyboardLayoutSetDisplayName(final InputMethodSubtype subtype) {
    261         final String layoutName = getKeyboardLayoutSetName(subtype);
    262         return getKeyboardLayoutSetDisplayName(layoutName);
    263     }
    264 
    265     public static String getKeyboardLayoutSetDisplayName(final String layoutName) {
    266         return sKeyboardLayoutToDisplayNameMap.get(layoutName);
    267     }
    268 
    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