Home | History | Annotate | Download | only in utils
      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.utils;
     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.DictionaryFactory;
     29 import com.android.inputmethod.latin.R;
     30 
     31 import java.util.HashMap;
     32 import java.util.Locale;
     33 
     34 public final class SubtypeLocaleUtils {
     35     static final String TAG = SubtypeLocaleUtils.class.getSimpleName();
     36     // This class must be located in the same package as LatinIME.java.
     37     private static final String RESOURCE_PACKAGE_NAME =
     38             DictionaryFactory.class.getPackage().getName();
     39 
     40     // Special language code to represent "no language".
     41     public static final String NO_LANGUAGE = "zz";
     42     public static final String QWERTY = "qwerty";
     43     public static final String EMOJI = "emoji";
     44     public static final int UNKNOWN_KEYBOARD_LAYOUT = R.string.subtype_generic;
     45 
     46     private static boolean sInitialized = false;
     47     private static Resources sResources;
     48     private static String[] sPredefinedKeyboardLayoutSet;
     49     // Keyboard layout to its display name map.
     50     private static final HashMap<String, String> sKeyboardLayoutToDisplayNameMap =
     51             CollectionUtils.newHashMap();
     52     // Keyboard layout to subtype name resource id map.
     53     private static final HashMap<String, Integer> sKeyboardLayoutToNameIdsMap =
     54             CollectionUtils.newHashMap();
     55     // Exceptional locale to subtype name resource id map.
     56     private static final HashMap<String, Integer> sExceptionalLocaleToNameIdsMap =
     57             CollectionUtils.newHashMap();
     58     // Exceptional locale to subtype name with layout resource id map.
     59     private static final HashMap<String, Integer> sExceptionalLocaleToWithLayoutNameIdsMap =
     60             CollectionUtils.newHashMap();
     61     private static final String SUBTYPE_NAME_RESOURCE_PREFIX =
     62             "string/subtype_";
     63     private static final String SUBTYPE_NAME_RESOURCE_GENERIC_PREFIX =
     64             "string/subtype_generic_";
     65     private static final String SUBTYPE_NAME_RESOURCE_WITH_LAYOUT_PREFIX =
     66             "string/subtype_with_layout_";
     67     private static final String SUBTYPE_NAME_RESOURCE_NO_LANGUAGE_PREFIX =
     68             "string/subtype_no_language_";
     69     // Keyboard layout set name for the subtypes that don't have a keyboardLayoutSet extra value.
     70     // This is for compatibility to keep the same subtype ids as pre-JellyBean.
     71     private static final HashMap<String, String> sLocaleAndExtraValueToKeyboardLayoutSetMap =
     72             CollectionUtils.newHashMap();
     73 
     74     private SubtypeLocaleUtils() {
     75         // Intentional empty constructor for utility class.
     76     }
     77 
     78     // Note that this initialization method can be called multiple times.
     79     public static synchronized void init(final Context context) {
     80         if (sInitialized) return;
     81 
     82         final Resources res = context.getResources();
     83         sResources = res;
     84 
     85         final String[] predefinedLayoutSet = res.getStringArray(R.array.predefined_layouts);
     86         sPredefinedKeyboardLayoutSet = predefinedLayoutSet;
     87         final String[] layoutDisplayNames = res.getStringArray(
     88                 R.array.predefined_layout_display_names);
     89         for (int i = 0; i < predefinedLayoutSet.length; i++) {
     90             final String layoutName = predefinedLayoutSet[i];
     91             sKeyboardLayoutToDisplayNameMap.put(layoutName, layoutDisplayNames[i]);
     92             final String resourceName = SUBTYPE_NAME_RESOURCE_GENERIC_PREFIX + layoutName;
     93             final int resId = res.getIdentifier(resourceName, null, RESOURCE_PACKAGE_NAME);
     94             sKeyboardLayoutToNameIdsMap.put(layoutName, resId);
     95             // Register subtype name resource id of "No language" with key "zz_<layout>"
     96             final String noLanguageResName = SUBTYPE_NAME_RESOURCE_NO_LANGUAGE_PREFIX + layoutName;
     97             final int noLanguageResId = res.getIdentifier(
     98                     noLanguageResName, null, RESOURCE_PACKAGE_NAME);
     99             final String key = getNoLanguageLayoutKey(layoutName);
    100             sKeyboardLayoutToNameIdsMap.put(key, noLanguageResId);
    101         }
    102 
    103         final String[] exceptionalLocales = res.getStringArray(
    104                 R.array.subtype_locale_exception_keys);
    105         for (int i = 0; i < exceptionalLocales.length; i++) {
    106             final String localeString = exceptionalLocales[i];
    107             final String resourceName = SUBTYPE_NAME_RESOURCE_PREFIX + localeString;
    108             final int resId = res.getIdentifier(resourceName, null, RESOURCE_PACKAGE_NAME);
    109             sExceptionalLocaleToNameIdsMap.put(localeString, resId);
    110             final String resourceNameWithLayout =
    111                     SUBTYPE_NAME_RESOURCE_WITH_LAYOUT_PREFIX + localeString;
    112             final int resIdWithLayout = res.getIdentifier(
    113                     resourceNameWithLayout, null, RESOURCE_PACKAGE_NAME);
    114             sExceptionalLocaleToWithLayoutNameIdsMap.put(localeString, resIdWithLayout);
    115         }
    116 
    117         final String[] keyboardLayoutSetMap = res.getStringArray(
    118                 R.array.locale_and_extra_value_to_keyboard_layout_set_map);
    119         for (int i = 0; i + 1 < keyboardLayoutSetMap.length; i += 2) {
    120             final String key = keyboardLayoutSetMap[i];
    121             final String keyboardLayoutSet = keyboardLayoutSetMap[i + 1];
    122             sLocaleAndExtraValueToKeyboardLayoutSetMap.put(key, keyboardLayoutSet);
    123         }
    124 
    125         sInitialized = true;
    126     }
    127 
    128     public static String[] getPredefinedKeyboardLayoutSet() {
    129         return sPredefinedKeyboardLayoutSet;
    130     }
    131 
    132     public static boolean isExceptionalLocale(final String localeString) {
    133         return sExceptionalLocaleToNameIdsMap.containsKey(localeString);
    134     }
    135 
    136     private static final String getNoLanguageLayoutKey(final String keyboardLayoutName) {
    137         return NO_LANGUAGE + "_" + keyboardLayoutName;
    138     }
    139 
    140     public static int getSubtypeNameId(final String localeString, final String keyboardLayoutName) {
    141         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN
    142                 && isExceptionalLocale(localeString)) {
    143             return sExceptionalLocaleToWithLayoutNameIdsMap.get(localeString);
    144         }
    145         final String key = NO_LANGUAGE.equals(localeString)
    146                 ? getNoLanguageLayoutKey(keyboardLayoutName)
    147                 : keyboardLayoutName;
    148         final Integer nameId = sKeyboardLayoutToNameIdsMap.get(key);
    149         return nameId == null ? UNKNOWN_KEYBOARD_LAYOUT : nameId;
    150     }
    151 
    152     private static Locale getDisplayLocaleOfSubtypeLocale(final String localeString) {
    153         if (NO_LANGUAGE.equals(localeString)) {
    154             return sResources.getConfiguration().locale;
    155         }
    156         return LocaleUtils.constructLocaleFromString(localeString);
    157     }
    158 
    159     public static String getSubtypeLocaleDisplayNameInSystemLocale(final String localeString) {
    160         final Locale displayLocale = sResources.getConfiguration().locale;
    161         return getSubtypeLocaleDisplayNameInternal(localeString, displayLocale);
    162     }
    163 
    164     public static String getSubtypeLocaleDisplayName(final String localeString) {
    165         final Locale displayLocale = getDisplayLocaleOfSubtypeLocale(localeString);
    166         return getSubtypeLocaleDisplayNameInternal(localeString, displayLocale);
    167     }
    168 
    169     private static String getSubtypeLocaleDisplayNameInternal(final String localeString,
    170             final Locale displayLocale) {
    171         final Integer exceptionalNameResId = sExceptionalLocaleToNameIdsMap.get(localeString);
    172         final String displayName;
    173         if (exceptionalNameResId != null) {
    174             final RunInLocale<String> getExceptionalName = new RunInLocale<String>() {
    175                 @Override
    176                 protected String job(final Resources res) {
    177                     return res.getString(exceptionalNameResId);
    178                 }
    179             };
    180             displayName = getExceptionalName.runInLocale(sResources, displayLocale);
    181         } else if (NO_LANGUAGE.equals(localeString)) {
    182             // No language subtype should be displayed in system locale.
    183             return sResources.getString(R.string.subtype_no_language);
    184         } else {
    185             final Locale locale = LocaleUtils.constructLocaleFromString(localeString);
    186             displayName = locale.getDisplayName(displayLocale);
    187         }
    188         return StringUtils.capitalizeFirstCodePoint(displayName, displayLocale);
    189     }
    190 
    191     // InputMethodSubtype's display name in its locale.
    192     //        isAdditionalSubtype (T=true, F=false)
    193     // locale layout  |  display name
    194     // ------ ------- - ----------------------
    195     //  en_US qwerty  F  English (US)            exception
    196     //  en_GB qwerty  F  English (UK)            exception
    197     //  es_US spanish F  Espaol (EE.UU.)        exception
    198     //  fr    azerty  F  Franais
    199     //  fr_CA qwerty  F  Franais (Canada)
    200     //  de    qwertz  F  Deutsch
    201     //  zz    qwerty  F  No language (QWERTY)    in system locale
    202     //  fr    qwertz  T  Franais (QWERTZ)
    203     //  de    qwerty  T  Deutsch (QWERTY)
    204     //  en_US azerty  T  English (US) (AZERTY)   exception
    205     //  zz    azerty  T  No language (AZERTY)    in system locale
    206 
    207     private static String getReplacementString(final InputMethodSubtype subtype,
    208             final Locale displayLocale) {
    209         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN
    210                 && subtype.containsExtraValueKey(UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME)) {
    211             return subtype.getExtraValueOf(UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME);
    212         } else {
    213             return getSubtypeLocaleDisplayNameInternal(subtype.getLocale(), displayLocale);
    214         }
    215     }
    216 
    217     public static String getSubtypeDisplayNameInSystemLocale(final InputMethodSubtype subtype) {
    218         final Locale displayLocale = sResources.getConfiguration().locale;
    219         return getSubtypeDisplayNameInternal(subtype, displayLocale);
    220     }
    221 
    222     public static String getSubtypeNameForLogging(final InputMethodSubtype subtype) {
    223         if (subtype == null) {
    224             return "<null subtype>";
    225         }
    226         return getSubtypeLocale(subtype) + "/" + getKeyboardLayoutSetName(subtype);
    227     }
    228 
    229     private static String getSubtypeDisplayNameInternal(final InputMethodSubtype subtype,
    230             final Locale displayLocale) {
    231         final String replacementString = getReplacementString(subtype, displayLocale);
    232         final int nameResId = subtype.getNameResId();
    233         final RunInLocale<String> getSubtypeName = new RunInLocale<String>() {
    234             @Override
    235             protected String job(final Resources res) {
    236                 try {
    237                     return res.getString(nameResId, replacementString);
    238                 } catch (Resources.NotFoundException e) {
    239                     // TODO: Remove this catch when InputMethodManager.getCurrentInputMethodSubtype
    240                     // is fixed.
    241                     Log.w(TAG, "Unknown subtype: mode=" + subtype.getMode()
    242                             + " nameResId=" + subtype.getNameResId()
    243                             + " locale=" + subtype.getLocale()
    244                             + " extra=" + subtype.getExtraValue()
    245                             + "\n" + DebugLogUtils.getStackTrace());
    246                     return "";
    247                 }
    248             }
    249         };
    250         return StringUtils.capitalizeFirstCodePoint(
    251                 getSubtypeName.runInLocale(sResources, displayLocale), displayLocale);
    252     }
    253 
    254     public static boolean isNoLanguage(final InputMethodSubtype subtype) {
    255         final String localeString = subtype.getLocale();
    256         return NO_LANGUAGE.equals(localeString);
    257     }
    258 
    259     public static Locale getSubtypeLocale(final InputMethodSubtype subtype) {
    260         final String localeString = subtype.getLocale();
    261         return LocaleUtils.constructLocaleFromString(localeString);
    262     }
    263 
    264     public static String getKeyboardLayoutSetDisplayName(final InputMethodSubtype subtype) {
    265         final String layoutName = getKeyboardLayoutSetName(subtype);
    266         return getKeyboardLayoutSetDisplayName(layoutName);
    267     }
    268 
    269     public static String getKeyboardLayoutSetDisplayName(final String layoutName) {
    270         return sKeyboardLayoutToDisplayNameMap.get(layoutName);
    271     }
    272 
    273     public static String getKeyboardLayoutSetName(final InputMethodSubtype subtype) {
    274         String keyboardLayoutSet = subtype.getExtraValueOf(KEYBOARD_LAYOUT_SET);
    275         if (keyboardLayoutSet == null) {
    276             // This subtype doesn't have a keyboardLayoutSet extra value, so lookup its keyboard
    277             // layout set in sLocaleAndExtraValueToKeyboardLayoutSetMap to keep it compatible with
    278             // pre-JellyBean.
    279             final String key = subtype.getLocale() + ":" + subtype.getExtraValue();
    280             keyboardLayoutSet = sLocaleAndExtraValueToKeyboardLayoutSetMap.get(key);
    281         }
    282         // TODO: Remove this null check when InputMethodManager.getCurrentInputMethodSubtype is
    283         // fixed.
    284         if (keyboardLayoutSet == null) {
    285             android.util.Log.w(TAG, "KeyboardLayoutSet not found, use QWERTY: " +
    286                     "locale=" + subtype.getLocale() + " extraValue=" + subtype.getExtraValue());
    287             return QWERTY;
    288         }
    289         return keyboardLayoutSet;
    290     }
    291 
    292     // InputMethodSubtype's display name for spacebar text in its locale.
    293     //        isAdditionalSubtype (T=true, F=false)
    294     // locale layout  | Short  Middle      Full
    295     // ------ ------- - ---- --------- ----------------------
    296     //  en_US qwerty  F  En  English   English (US)           exception
    297     //  en_GB qwerty  F  En  English   English (UK)           exception
    298     //  es_US spanish F  Es  Espaol   Espaol (EE.UU.)       exception
    299     //  fr    azerty  F  Fr  Franais  Franais
    300     //  fr_CA qwerty  F  Fr  Franais  Franais (Canada)
    301     //  de    qwertz  F  De  Deutsch   Deutsch
    302     //  zz    qwerty  F      QWERTY    QWERTY
    303     //  fr    qwertz  T  Fr  Franais  Franais
    304     //  de    qwerty  T  De  Deutsch   Deutsch
    305     //  en_US azerty  T  En  English   English (US)
    306     //  zz    azerty  T      AZERTY    AZERTY
    307 
    308     // Get InputMethodSubtype's full display name in its locale.
    309     public static String getFullDisplayName(final InputMethodSubtype subtype) {
    310         if (isNoLanguage(subtype)) {
    311             return getKeyboardLayoutSetDisplayName(subtype);
    312         }
    313         return getSubtypeLocaleDisplayName(subtype.getLocale());
    314     }
    315 
    316     // Get InputMethodSubtype's middle display name in its locale.
    317     public static String getMiddleDisplayName(final InputMethodSubtype subtype) {
    318         if (isNoLanguage(subtype)) {
    319             return getKeyboardLayoutSetDisplayName(subtype);
    320         }
    321         final Locale locale = getSubtypeLocale(subtype);
    322         return getSubtypeLocaleDisplayName(locale.getLanguage());
    323     }
    324 
    325     // Get InputMethodSubtype's short display name in its locale.
    326     public static String getShortDisplayName(final InputMethodSubtype subtype) {
    327         if (isNoLanguage(subtype)) {
    328             return "";
    329         }
    330         final Locale locale = getSubtypeLocale(subtype);
    331         return StringUtils.capitalizeFirstCodePoint(locale.getLanguage(), locale);
    332     }
    333 }
    334