| /* |
| * Copyright (C) 2011 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.inputmethod.latin.utils; |
| |
| import static com.android.inputmethod.latin.Constants.Subtype.ExtraValue.KEYBOARD_LAYOUT_SET; |
| import static com.android.inputmethod.latin.Constants.Subtype.ExtraValue.UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME; |
| |
| import android.content.Context; |
| import android.content.res.Resources; |
| import android.os.Build; |
| import android.util.Log; |
| import android.view.inputmethod.InputMethodSubtype; |
| |
| import com.android.inputmethod.latin.Constants; |
| import com.android.inputmethod.latin.R; |
| |
| import java.util.Arrays; |
| import java.util.HashMap; |
| import java.util.Locale; |
| |
| public final class SubtypeLocaleUtils { |
| private static final String TAG = SubtypeLocaleUtils.class.getSimpleName(); |
| |
| // This reference class {@link Constants} must be located in the same package as LatinIME.java. |
| private static final String RESOURCE_PACKAGE_NAME = Constants.class.getPackage().getName(); |
| |
| // Special language code to represent "no language". |
| public static final String NO_LANGUAGE = "zz"; |
| public static final String QWERTY = "qwerty"; |
| public static final String EMOJI = "emoji"; |
| public static final int UNKNOWN_KEYBOARD_LAYOUT = R.string.subtype_generic; |
| |
| private static volatile boolean sInitialized = false; |
| private static final Object sInitializeLock = new Object(); |
| private static Resources sResources; |
| private static String[] sPredefinedKeyboardLayoutSet; |
| // Keyboard layout to its display name map. |
| private static final HashMap<String, String> sKeyboardLayoutToDisplayNameMap = new HashMap<>(); |
| // Keyboard layout to subtype name resource id map. |
| private static final HashMap<String, Integer> sKeyboardLayoutToNameIdsMap = new HashMap<>(); |
| // Exceptional locale to subtype name resource id map. |
| private static final HashMap<String, Integer> sExceptionalLocaleToNameIdsMap = new HashMap<>(); |
| // Exceptional locale to subtype name with layout resource id map. |
| private static final HashMap<String, Integer> sExceptionalLocaleToWithLayoutNameIdsMap = |
| new HashMap<>(); |
| private static final String SUBTYPE_NAME_RESOURCE_PREFIX = |
| "string/subtype_"; |
| private static final String SUBTYPE_NAME_RESOURCE_GENERIC_PREFIX = |
| "string/subtype_generic_"; |
| private static final String SUBTYPE_NAME_RESOURCE_WITH_LAYOUT_PREFIX = |
| "string/subtype_with_layout_"; |
| private static final String SUBTYPE_NAME_RESOURCE_NO_LANGUAGE_PREFIX = |
| "string/subtype_no_language_"; |
| // Keyboard layout set name for the subtypes that don't have a keyboardLayoutSet extra value. |
| // This is for compatibility to keep the same subtype ids as pre-JellyBean. |
| private static final HashMap<String, String> sLocaleAndExtraValueToKeyboardLayoutSetMap = |
| new HashMap<>(); |
| |
| private SubtypeLocaleUtils() { |
| // Intentional empty constructor for utility class. |
| } |
| |
| // Note that this initialization method can be called multiple times. |
| public static void init(final Context context) { |
| synchronized (sInitializeLock) { |
| if (sInitialized == false) { |
| initLocked(context); |
| sInitialized = true; |
| } |
| } |
| } |
| |
| private static void initLocked(final Context context) { |
| final Resources res = context.getResources(); |
| sResources = res; |
| |
| final String[] predefinedLayoutSet = res.getStringArray(R.array.predefined_layouts); |
| sPredefinedKeyboardLayoutSet = predefinedLayoutSet; |
| final String[] layoutDisplayNames = res.getStringArray( |
| R.array.predefined_layout_display_names); |
| for (int i = 0; i < predefinedLayoutSet.length; i++) { |
| final String layoutName = predefinedLayoutSet[i]; |
| sKeyboardLayoutToDisplayNameMap.put(layoutName, layoutDisplayNames[i]); |
| final String resourceName = SUBTYPE_NAME_RESOURCE_GENERIC_PREFIX + layoutName; |
| final int resId = res.getIdentifier(resourceName, null, RESOURCE_PACKAGE_NAME); |
| sKeyboardLayoutToNameIdsMap.put(layoutName, resId); |
| // Register subtype name resource id of "No language" with key "zz_<layout>" |
| final String noLanguageResName = SUBTYPE_NAME_RESOURCE_NO_LANGUAGE_PREFIX + layoutName; |
| final int noLanguageResId = res.getIdentifier( |
| noLanguageResName, null, RESOURCE_PACKAGE_NAME); |
| final String key = getNoLanguageLayoutKey(layoutName); |
| sKeyboardLayoutToNameIdsMap.put(key, noLanguageResId); |
| } |
| |
| final String[] exceptionalLocales = res.getStringArray( |
| R.array.subtype_locale_exception_keys); |
| for (int i = 0; i < exceptionalLocales.length; i++) { |
| final String localeString = exceptionalLocales[i]; |
| final String resourceName = SUBTYPE_NAME_RESOURCE_PREFIX + localeString; |
| final int resId = res.getIdentifier(resourceName, null, RESOURCE_PACKAGE_NAME); |
| sExceptionalLocaleToNameIdsMap.put(localeString, resId); |
| final String resourceNameWithLayout = |
| SUBTYPE_NAME_RESOURCE_WITH_LAYOUT_PREFIX + localeString; |
| final int resIdWithLayout = res.getIdentifier( |
| resourceNameWithLayout, null, RESOURCE_PACKAGE_NAME); |
| sExceptionalLocaleToWithLayoutNameIdsMap.put(localeString, resIdWithLayout); |
| } |
| |
| final String[] keyboardLayoutSetMap = res.getStringArray( |
| R.array.locale_and_extra_value_to_keyboard_layout_set_map); |
| for (int i = 0; i + 1 < keyboardLayoutSetMap.length; i += 2) { |
| final String key = keyboardLayoutSetMap[i]; |
| final String keyboardLayoutSet = keyboardLayoutSetMap[i + 1]; |
| sLocaleAndExtraValueToKeyboardLayoutSetMap.put(key, keyboardLayoutSet); |
| } |
| } |
| |
| public static String[] getPredefinedKeyboardLayoutSet() { |
| return sPredefinedKeyboardLayoutSet; |
| } |
| |
| public static boolean isExceptionalLocale(final String localeString) { |
| return sExceptionalLocaleToNameIdsMap.containsKey(localeString); |
| } |
| |
| private static final String getNoLanguageLayoutKey(final String keyboardLayoutName) { |
| return NO_LANGUAGE + "_" + keyboardLayoutName; |
| } |
| |
| public static int getSubtypeNameId(final String localeString, final String keyboardLayoutName) { |
| if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN |
| && isExceptionalLocale(localeString)) { |
| return sExceptionalLocaleToWithLayoutNameIdsMap.get(localeString); |
| } |
| final String key = NO_LANGUAGE.equals(localeString) |
| ? getNoLanguageLayoutKey(keyboardLayoutName) |
| : keyboardLayoutName; |
| final Integer nameId = sKeyboardLayoutToNameIdsMap.get(key); |
| return nameId == null ? UNKNOWN_KEYBOARD_LAYOUT : nameId; |
| } |
| |
| private static Locale getDisplayLocaleOfSubtypeLocale(final String localeString) { |
| if (NO_LANGUAGE.equals(localeString)) { |
| return sResources.getConfiguration().locale; |
| } |
| return LocaleUtils.constructLocaleFromString(localeString); |
| } |
| |
| public static String getSubtypeLocaleDisplayNameInSystemLocale(final String localeString) { |
| final Locale displayLocale = sResources.getConfiguration().locale; |
| return getSubtypeLocaleDisplayNameInternal(localeString, displayLocale); |
| } |
| |
| public static String getSubtypeLocaleDisplayName(final String localeString) { |
| final Locale displayLocale = getDisplayLocaleOfSubtypeLocale(localeString); |
| return getSubtypeLocaleDisplayNameInternal(localeString, displayLocale); |
| } |
| |
| public static String getSubtypeLanguageDisplayName(final String localeString) { |
| final Locale locale = LocaleUtils.constructLocaleFromString(localeString); |
| final Locale displayLocale = getDisplayLocaleOfSubtypeLocale(localeString); |
| return getSubtypeLocaleDisplayNameInternal(locale.getLanguage(), displayLocale); |
| } |
| |
| private static String getSubtypeLocaleDisplayNameInternal(final String localeString, |
| final Locale displayLocale) { |
| if (NO_LANGUAGE.equals(localeString)) { |
| // No language subtype should be displayed in system locale. |
| return sResources.getString(R.string.subtype_no_language); |
| } |
| final Integer exceptionalNameResId = sExceptionalLocaleToNameIdsMap.get(localeString); |
| final String displayName; |
| if (exceptionalNameResId != null) { |
| final RunInLocale<String> getExceptionalName = new RunInLocale<String>() { |
| @Override |
| protected String job(final Resources res) { |
| return res.getString(exceptionalNameResId); |
| } |
| }; |
| displayName = getExceptionalName.runInLocale(sResources, displayLocale); |
| } else { |
| final Locale locale = LocaleUtils.constructLocaleFromString(localeString); |
| displayName = locale.getDisplayName(displayLocale); |
| } |
| return StringUtils.capitalizeFirstCodePoint(displayName, displayLocale); |
| } |
| |
| // InputMethodSubtype's display name in its locale. |
| // isAdditionalSubtype (T=true, F=false) |
| // locale layout | display name |
| // ------ ------- - ---------------------- |
| // en_US qwerty F English (US) exception |
| // en_GB qwerty F English (UK) exception |
| // es_US spanish F Español (EE.UU.) exception |
| // fr azerty F Français |
| // fr_CA qwerty F Français (Canada) |
| // fr_CH swiss F Français (Suisse) |
| // de qwertz F Deutsch |
| // de_CH swiss T Deutsch (Schweiz) |
| // zz qwerty F Alphabet (QWERTY) in system locale |
| // fr qwertz T Français (QWERTZ) |
| // de qwerty T Deutsch (QWERTY) |
| // en_US azerty T English (US) (AZERTY) exception |
| // zz azerty T Alphabet (AZERTY) in system locale |
| |
| private static String getReplacementString(final InputMethodSubtype subtype, |
| final Locale displayLocale) { |
| if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN |
| && subtype.containsExtraValueKey(UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME)) { |
| return subtype.getExtraValueOf(UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME); |
| } else { |
| return getSubtypeLocaleDisplayNameInternal(subtype.getLocale(), displayLocale); |
| } |
| } |
| |
| public static String getSubtypeDisplayNameInSystemLocale(final InputMethodSubtype subtype) { |
| final Locale displayLocale = sResources.getConfiguration().locale; |
| return getSubtypeDisplayNameInternal(subtype, displayLocale); |
| } |
| |
| public static String getSubtypeNameForLogging(final InputMethodSubtype subtype) { |
| if (subtype == null) { |
| return "<null subtype>"; |
| } |
| return getSubtypeLocale(subtype) + "/" + getKeyboardLayoutSetName(subtype); |
| } |
| |
| private static String getSubtypeDisplayNameInternal(final InputMethodSubtype subtype, |
| final Locale displayLocale) { |
| final String replacementString = getReplacementString(subtype, displayLocale); |
| final int nameResId = subtype.getNameResId(); |
| final RunInLocale<String> getSubtypeName = new RunInLocale<String>() { |
| @Override |
| protected String job(final Resources res) { |
| try { |
| return res.getString(nameResId, replacementString); |
| } catch (Resources.NotFoundException e) { |
| // TODO: Remove this catch when InputMethodManager.getCurrentInputMethodSubtype |
| // is fixed. |
| Log.w(TAG, "Unknown subtype: mode=" + subtype.getMode() |
| + " nameResId=" + subtype.getNameResId() |
| + " locale=" + subtype.getLocale() |
| + " extra=" + subtype.getExtraValue() |
| + "\n" + DebugLogUtils.getStackTrace()); |
| return ""; |
| } |
| } |
| }; |
| return StringUtils.capitalizeFirstCodePoint( |
| getSubtypeName.runInLocale(sResources, displayLocale), displayLocale); |
| } |
| |
| public static boolean isNoLanguage(final InputMethodSubtype subtype) { |
| final String localeString = subtype.getLocale(); |
| return NO_LANGUAGE.equals(localeString); |
| } |
| |
| public static Locale getSubtypeLocale(final InputMethodSubtype subtype) { |
| final String localeString = subtype.getLocale(); |
| return LocaleUtils.constructLocaleFromString(localeString); |
| } |
| |
| public static String getKeyboardLayoutSetDisplayName(final InputMethodSubtype subtype) { |
| final String layoutName = getKeyboardLayoutSetName(subtype); |
| return getKeyboardLayoutSetDisplayName(layoutName); |
| } |
| |
| public static String getKeyboardLayoutSetDisplayName(final String layoutName) { |
| return sKeyboardLayoutToDisplayNameMap.get(layoutName); |
| } |
| |
| public static String getKeyboardLayoutSetName(final InputMethodSubtype subtype) { |
| String keyboardLayoutSet = subtype.getExtraValueOf(KEYBOARD_LAYOUT_SET); |
| if (keyboardLayoutSet == null) { |
| // This subtype doesn't have a keyboardLayoutSet extra value, so lookup its keyboard |
| // layout set in sLocaleAndExtraValueToKeyboardLayoutSetMap to keep it compatible with |
| // pre-JellyBean. |
| final String key = subtype.getLocale() + ":" + subtype.getExtraValue(); |
| keyboardLayoutSet = sLocaleAndExtraValueToKeyboardLayoutSetMap.get(key); |
| } |
| // TODO: Remove this null check when InputMethodManager.getCurrentInputMethodSubtype is |
| // fixed. |
| if (keyboardLayoutSet == null) { |
| android.util.Log.w(TAG, "KeyboardLayoutSet not found, use QWERTY: " + |
| "locale=" + subtype.getLocale() + " extraValue=" + subtype.getExtraValue()); |
| return QWERTY; |
| } |
| return keyboardLayoutSet; |
| } |
| |
| // TODO: Get this information from the framework instead of maintaining here by ourselves. |
| // Sorted list of known Right-To-Left language codes. |
| private static final String[] SORTED_RTL_LANGUAGES = { |
| "ar", // Arabic |
| "fa", // Persian |
| "iw", // Hebrew |
| }; |
| static { |
| Arrays.sort(SORTED_RTL_LANGUAGES); |
| } |
| |
| public static boolean isRtlLanguage(final Locale locale) { |
| final String language = locale.getLanguage(); |
| return Arrays.binarySearch(SORTED_RTL_LANGUAGES, language) >= 0; |
| } |
| |
| public static boolean isRtlLanguage(final InputMethodSubtype subtype) { |
| return isRtlLanguage(getSubtypeLocale(subtype)); |
| } |
| |
| public static String getCombiningRulesExtraValue(final InputMethodSubtype subtype) { |
| return subtype.getExtraValueOf(Constants.Subtype.ExtraValue.COMBINING_RULES); |
| } |
| } |