From 82e6d8a5cbef051caa73f35096274b643291c34c Mon Sep 17 00:00:00 2001 From: Helium314 Date: Sat, 24 May 2025 08:25:18 +0200 Subject: [PATCH] move SubtypeLocaleUtils to Kotlin --- .../latin/utils/SubtypeLocaleUtils.java | 318 ------------------ .../latin/utils/SubtypeLocaleUtils.kt | 264 +++++++++++++++ 2 files changed, 264 insertions(+), 318 deletions(-) delete mode 100644 app/src/main/java/helium314/keyboard/latin/utils/SubtypeLocaleUtils.java create mode 100644 app/src/main/java/helium314/keyboard/latin/utils/SubtypeLocaleUtils.kt diff --git a/app/src/main/java/helium314/keyboard/latin/utils/SubtypeLocaleUtils.java b/app/src/main/java/helium314/keyboard/latin/utils/SubtypeLocaleUtils.java deleted file mode 100644 index bb99355b6..000000000 --- a/app/src/main/java/helium314/keyboard/latin/utils/SubtypeLocaleUtils.java +++ /dev/null @@ -1,318 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * modified - * SPDX-License-Identifier: Apache-2.0 AND GPL-3.0-only - */ - -package helium314.keyboard.latin.utils; - -import android.content.Context; -import android.content.res.Resources; -import android.view.inputmethod.InputMethodSubtype; - -import helium314.keyboard.compat.ConfigurationCompatKt; -import helium314.keyboard.latin.R; -import helium314.keyboard.latin.common.Constants; -import helium314.keyboard.latin.common.LocaleUtils; -import helium314.keyboard.latin.common.StringUtils; - -import java.util.HashMap; -import java.util.Locale; - -import static helium314.keyboard.latin.common.Constants.Subtype.ExtraValue.COMBINING_RULES; -import static helium314.keyboard.latin.common.Constants.Subtype.ExtraValue.UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -/** - * A helper class to deal with subtype locales. - */ -// TODO: consolidate this into RichInputMethodSubtype -// todo (later): see whether this complicated mess can be simplified -public final class SubtypeLocaleUtils { - static final String TAG = SubtypeLocaleUtils.class.getSimpleName(); - - // This reference class {@link R} must be located in the same package as LatinIME.java. - // switched to context.getPackageName(), which works with changed debug package name - // any reason to prefer original version? -// private static final String RESOURCE_PACKAGE_NAME = R.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; - // Keyboard layout to its display name map. - private static final HashMap sKeyboardLayoutToDisplayNameMap = new HashMap<>(); - // Keyboard layout to subtype name resource id map. - private static final HashMap sKeyboardLayoutToNameIdsMap = new HashMap<>(); - // Exceptional locale whose name should be displayed in Locale.ROOT. - private static final HashMap sExceptionalLocaleDisplayedInRootLocale = new HashMap<>(); - // Exceptional locale to subtype name resource id map. - private static final HashMap sExceptionalLocaleToNameIdsMap = new HashMap<>(); - // Exceptional locale to subtype name with layout resource id map. - private static final HashMap sExceptionalLocaleToWithLayoutNameIdsMap = new HashMap<>(); - private static final HashMap sResourceSubtypeDisplayNames = 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_"; - private static final String SUBTYPE_NAME_RESOURCE_IN_ROOT_LOCALE_PREFIX = "string/subtype_in_root_locale_"; - - 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) { - initLocked(context); - sInitialized = true; - } - } - } - - private static void initLocked(final Context context) { - final String RESOURCE_PACKAGE_NAME = context.getPackageName(); - final Resources res = context.getResources(); - sResources = res; - - final String[] predefinedLayoutSet = res.getStringArray(R.array.predefined_layouts); - 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_" - 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[] exceptionalLocaleInRootLocale = res.getStringArray(R.array.subtype_locale_displayed_in_root_locale); - for (final String languageTag : exceptionalLocaleInRootLocale) { - final String resourceName = SUBTYPE_NAME_RESOURCE_IN_ROOT_LOCALE_PREFIX + languageTag.replace('-', '_'); - final int resId = res.getIdentifier(resourceName, null, RESOURCE_PACKAGE_NAME); - sExceptionalLocaleDisplayedInRootLocale.put(languageTag, resId); - } - - final String[] exceptionalLocales = res.getStringArray(R.array.subtype_locale_exception_keys); - for (final String languageTag : exceptionalLocales) { - final String resourceName = SUBTYPE_NAME_RESOURCE_PREFIX + languageTag.replace('-', '_'); - final int resId = res.getIdentifier(resourceName, null, RESOURCE_PACKAGE_NAME); - sExceptionalLocaleToNameIdsMap.put(languageTag, resId); - final String resourceNameWithLayout = SUBTYPE_NAME_RESOURCE_WITH_LAYOUT_PREFIX + languageTag.replace('-', '_'); - final int resIdWithLayout = res.getIdentifier(resourceNameWithLayout, null, RESOURCE_PACKAGE_NAME); - sExceptionalLocaleToWithLayoutNameIdsMap.put(languageTag, resIdWithLayout); - } - } - - public static boolean isExceptionalLocale(final Locale locale) { - return sExceptionalLocaleToNameIdsMap.containsKey(locale.toLanguageTag()); - } - - private static String getNoLanguageLayoutKey(final String keyboardLayoutName) { - return NO_LANGUAGE + "_" + keyboardLayoutName; - } - - public static int getSubtypeNameResId(final Locale locale, final String keyboardLayoutName) { - final String languageTag = locale.toLanguageTag(); - if (isExceptionalLocale(locale)) { - return sExceptionalLocaleToWithLayoutNameIdsMap.get(languageTag); - } - final String key = NO_LANGUAGE.equals(languageTag) - ? getNoLanguageLayoutKey(keyboardLayoutName) - : keyboardLayoutName; - final Integer nameId = sKeyboardLayoutToNameIdsMap.get(key); - return nameId == null ? UNKNOWN_KEYBOARD_LAYOUT : nameId; - } - - @NonNull - public static Locale getDisplayLocaleOfSubtypeLocale(@NonNull final Locale locale) { - final String languageTag = locale.toLanguageTag(); - if (NO_LANGUAGE.equals(languageTag)) { - return ConfigurationCompatKt.locale(sResources.getConfiguration()); - } - if (sExceptionalLocaleDisplayedInRootLocale.containsKey(languageTag)) { - return Locale.ROOT; - } - return locale; - } - - public static String getSubtypeLocaleDisplayNameInSystemLocale(@NonNull final Locale locale) { - final Locale displayLocale = ConfigurationCompatKt.locale(sResources.getConfiguration()); - return getSubtypeLocaleDisplayNameInternal(locale, displayLocale); - } - - @NonNull - public static String getSubtypeLocaleDisplayName(@NonNull final Locale locale) { - final Locale displayLocale = getDisplayLocaleOfSubtypeLocale(locale); - return getSubtypeLocaleDisplayNameInternal(locale, displayLocale); - } - - @NonNull - public static String getSubtypeLanguageDisplayName(@NonNull final Locale locale) { - final Locale displayLocale = getDisplayLocaleOfSubtypeLocale(locale); - final Locale languageLocale; - if (sExceptionalLocaleDisplayedInRootLocale.containsKey(locale.toLanguageTag())) { - languageLocale = locale; - } else { - languageLocale = LocaleUtils.constructLocale(locale.getLanguage()); - } - return getSubtypeLocaleDisplayNameInternal(languageLocale, displayLocale); - } - - @NonNull - private static String getSubtypeLocaleDisplayNameInternal(@NonNull final Locale locale, - @NonNull final Locale displayLocale) { - final String languageTag = locale.toLanguageTag(); - if (NO_LANGUAGE.equals(locale.toLanguageTag())) { - // No language subtype should be displayed in system locale. - return sResources.getString(R.string.subtype_no_language); - } - final Integer exceptionalNameResId; - if (displayLocale.equals(Locale.ROOT) - && sExceptionalLocaleDisplayedInRootLocale.containsKey(languageTag)) { - exceptionalNameResId = sExceptionalLocaleDisplayedInRootLocale.get(languageTag); - } else if (sExceptionalLocaleToNameIdsMap.containsKey(languageTag)) { - exceptionalNameResId = sExceptionalLocaleToNameIdsMap.get(languageTag); - } else { - exceptionalNameResId = null; - } - - final String displayName; - if (exceptionalNameResId != null) { - displayName = RunInLocaleKt.runInLocale(sResources, displayLocale, res -> res.getString(exceptionalNameResId)); - } else { - displayName = LocaleUtils.getLocaleDisplayNameInLocale(locale, sResources, 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 - - @NonNull - private static String getReplacementString(@NonNull final InputMethodSubtype subtype, - @NonNull final Locale displayLocale) { - if (subtype.containsExtraValueKey(UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME)) { - return subtype.getExtraValueOf(UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME); - } - return getSubtypeLocaleDisplayNameInternal(SubtypeUtilsKt.locale(subtype), displayLocale); - } - - @NonNull - public static String getDisplayNameInSystemLocale(@NonNull final String mainLayoutName, @NonNull final Locale locale) { - final String displayName = getMainLayoutDisplayName(mainLayoutName); - if (displayName != null) // works for custom and latin layouts - return displayName; - // we have some locale-specific layout - for (InputMethodSubtype subtype : SubtypeSettings.INSTANCE.getResourceSubtypesForLocale(locale)) { - final String main = LayoutType.Companion.getMainLayoutFromExtraValue(subtype.getExtraValue()); - if (mainLayoutName.equals(main)) - return getSubtypeDisplayNameInSystemLocale(subtype); - } - return mainLayoutName; // should never happen... - } - - @NonNull - public static String getSubtypeDisplayNameInSystemLocale(@NonNull final InputMethodSubtype subtype) { - final String cached = sResourceSubtypeDisplayNames.get(subtype.hashCode()); - if (cached != null) return cached; - - final Locale displayLocale = ConfigurationCompatKt.locale(sResources.getConfiguration()); - final String displayName = getSubtypeDisplayNameInternal(subtype, displayLocale); - - if (!subtype.containsExtraValueKey(Constants.Subtype.ExtraValue.IS_ADDITIONAL_SUBTYPE)) { - sResourceSubtypeDisplayNames.put(subtype.hashCode(), displayName); - } - return displayName; - } - - public static void clearDisplayNameCache() { - sResourceSubtypeDisplayNames.clear(); - } - - @NonNull - public static String getSubtypeNameForLogging(@Nullable final InputMethodSubtype subtype) { - if (subtype == null) { - return ""; - } - return SubtypeUtilsKt.locale(subtype) + "/" + getMainLayoutName(subtype); - } - - @NonNull - private static String getSubtypeDisplayNameInternal(@NonNull final InputMethodSubtype subtype, - @NonNull final Locale displayLocale) { - final String replacementString = getReplacementString(subtype, displayLocale); - final int nameResId = subtype.getNameResId(); - return RunInLocaleKt.runInLocale(sResources, displayLocale, - res -> { - try { - return StringUtils.capitalizeFirstCodePoint(res.getString(nameResId, replacementString), displayLocale); - } catch (Resources.NotFoundException e) { - Log.w(TAG, "Unknown subtype: mode=" + subtype.getMode() - + " nameResId=" + subtype.getNameResId() - + " locale=" + subtype.getLocale() - + " extra=" + subtype.getExtraValue() - + "\n" + DebugLogUtils.getStackTrace()); - return ""; - } - }); - } - - @Nullable - public static String getMainLayoutDisplayName(@NonNull final InputMethodSubtype subtype) { - final String layoutName = getMainLayoutName(subtype); - return getMainLayoutDisplayName(layoutName); - } - - @Nullable - public static String getMainLayoutDisplayName(@NonNull final String layoutName) { - if (LayoutUtilsCustom.INSTANCE.isCustomLayout(layoutName)) - return LayoutUtilsCustom.INSTANCE.getDisplayName(layoutName); - return sKeyboardLayoutToDisplayNameMap.get(layoutName); - } - - @NonNull - public static String getMainLayoutName(final InputMethodSubtype subtype) { - String mainLayoutName = SubtypeUtilsKt.mainLayoutName(subtype); - if (mainLayoutName == null && subtype.isAsciiCapable()) { - mainLayoutName = QWERTY; - } - if (mainLayoutName == null) { // we could search for a subtype with the correct script, but this is a bug anyway... - Log.w(TAG, "KeyboardLayoutSet not found, use QWERTY: " + - "locale=" + subtype.getLocale() + " extraValue=" + subtype.getExtraValue()); - return QWERTY; - } - return mainLayoutName; - } - - public static String getCombiningRulesExtraValue(final InputMethodSubtype subtype) { - return subtype.getExtraValueOf(COMBINING_RULES); - } -} diff --git a/app/src/main/java/helium314/keyboard/latin/utils/SubtypeLocaleUtils.kt b/app/src/main/java/helium314/keyboard/latin/utils/SubtypeLocaleUtils.kt new file mode 100644 index 000000000..8c19650d6 --- /dev/null +++ b/app/src/main/java/helium314/keyboard/latin/utils/SubtypeLocaleUtils.kt @@ -0,0 +1,264 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * modified + * SPDX-License-Identifier: Apache-2.0 AND GPL-3.0-only + */ +package helium314.keyboard.latin.utils + +import android.content.Context +import android.content.res.Resources +import android.view.inputmethod.InputMethodSubtype +import helium314.keyboard.compat.locale +import helium314.keyboard.latin.R +import helium314.keyboard.latin.common.Constants.Subtype.ExtraValue +import helium314.keyboard.latin.common.LocaleUtils.constructLocale +import helium314.keyboard.latin.common.LocaleUtils.getLocaleDisplayNameInLocale +import helium314.keyboard.latin.common.StringUtils +import helium314.keyboard.latin.utils.LayoutType.Companion.getMainLayoutFromExtraValue +import helium314.keyboard.latin.utils.LayoutUtilsCustom.getDisplayName +import helium314.keyboard.latin.utils.LayoutUtilsCustom.isCustomLayout +import helium314.keyboard.latin.utils.SubtypeSettings.getResourceSubtypesForLocale +import java.util.Locale +import kotlin.concurrent.Volatile + +/** + * A helper class to deal with subtype locales. + */ +object SubtypeLocaleUtils { + @Volatile + private var initialized = false + private lateinit var resources: Resources + + // Keyboard layout to its display name map. + private val keyboardLayoutToDisplayName = HashMap() + + // Keyboard layout to subtype name resource id map. + private val keyboardLayoutToNameIds = HashMap() + + // Exceptional locale whose name should be displayed in Locale.ROOT. + private val exceptionalLocaleDisplayedInRootLocale = HashMap() + + // Exceptional locale to subtype name resource id map. + private val exceptionalLocaleToNameIds = HashMap() + + // Exceptional locale to subtype name with layout resource id map. + private val exceptionalLocaleToWithLayoutNameIds = HashMap() + private val resourceSubtypeDisplayNames = HashMap() + + // Note that this initialization method can be called multiple times. + @JvmStatic + fun init(context: Context) { + synchronized(this) { + if (!initialized) { + initLocked(context) + initialized = true + } + } + } + + private fun initLocked(context: Context) { + val packageName = context.packageName + resources = context.resources + + // todo: layout names are currently translatable in subtype_no_language_* but not the default names + // just remove the separate "alphabet ()" strings and have a single "alphabet (%s)"? + // or rather use the same style as for languages and only have "alphabet" + val predefinedLayouts = resources.getStringArray(R.array.predefined_layouts) + val layoutDisplayNames = resources.getStringArray(R.array.predefined_layout_display_names) + for (i in predefinedLayouts.indices) { + val layoutName = predefinedLayouts[i] + keyboardLayoutToDisplayName[layoutName] = layoutDisplayNames[i] + val resourceName = SUBTYPE_NAME_RESOURCE_GENERIC_PREFIX + layoutName + val resId = resources.getIdentifier(resourceName, null, packageName) + keyboardLayoutToNameIds[layoutName] = resId + // Register subtype name resource id of "No language" with key "zz_" + val noLanguageResName = SUBTYPE_NAME_RESOURCE_NO_LANGUAGE_PREFIX + layoutName + val noLanguageResId = resources.getIdentifier(noLanguageResName, null, packageName) + val key = getNoLanguageLayoutKey(layoutName) + keyboardLayoutToNameIds[key] = noLanguageResId + } + + // todo: do it using 2 arrays like predefined_layouts (and adjust information in layouts.md) + val exceptionalLocaleInRootLocale = resources.getStringArray(R.array.subtype_locale_displayed_in_root_locale) + for (languageTag in exceptionalLocaleInRootLocale) { + val resourceName = SUBTYPE_NAME_RESOURCE_IN_ROOT_LOCALE_PREFIX + languageTag.replace('-', '_') + val resId = resources.getIdentifier(resourceName, null, packageName) + exceptionalLocaleDisplayedInRootLocale[languageTag] = resId + } + + // todo: do it using 2 arrays like predefined_layouts (and adjust information in layouts.md) + // and the _with_layout variants can be removed? + val exceptionalLocales = resources.getStringArray(R.array.subtype_locale_exception_keys) + for (languageTag in exceptionalLocales) { + val resourceName = SUBTYPE_NAME_RESOURCE_PREFIX + languageTag.replace('-', '_') + val resId = resources.getIdentifier(resourceName, null, packageName) + exceptionalLocaleToNameIds[languageTag] = resId + val resourceNameWithLayout = SUBTYPE_NAME_RESOURCE_WITH_LAYOUT_PREFIX + languageTag.replace('-', '_') + val resIdWithLayout = resources.getIdentifier(resourceNameWithLayout, null, packageName) + exceptionalLocaleToWithLayoutNameIds[languageTag] = resIdWithLayout + } + } + + fun isExceptionalLocale(locale: Locale): Boolean { + return exceptionalLocaleToNameIds.containsKey(locale.toLanguageTag()) + } + + private fun getNoLanguageLayoutKey(keyboardLayoutName: String): String { + return NO_LANGUAGE + "_" + keyboardLayoutName + } + + fun getSubtypeNameResId(locale: Locale, keyboardLayoutName: String): Int { + val languageTag = locale.toLanguageTag() + if (isExceptionalLocale(locale)) { + return exceptionalLocaleToWithLayoutNameIds[languageTag]!! + } + val key = if (languageTag == NO_LANGUAGE) getNoLanguageLayoutKey(keyboardLayoutName) + else keyboardLayoutName + return keyboardLayoutToNameIds[key] ?: UNKNOWN_KEYBOARD_LAYOUT + } + + private fun getDisplayLocaleOfSubtypeLocale(locale: Locale): Locale { + val languageTag = locale.toLanguageTag() + if (languageTag == NO_LANGUAGE) + return resources.configuration.locale() + if (exceptionalLocaleDisplayedInRootLocale.containsKey(languageTag)) + return Locale.ROOT + return locale + } + + fun getSubtypeLocaleDisplayNameInSystemLocale(locale: Locale): String { + val displayLocale = resources.configuration.locale() + return getSubtypeLocaleDisplayNameInternal(locale, displayLocale) + } + + fun getSubtypeLocaleDisplayName(locale: Locale): String { + val displayLocale = getDisplayLocaleOfSubtypeLocale(locale) + return getSubtypeLocaleDisplayNameInternal(locale, displayLocale) + } + + fun getSubtypeLanguageDisplayName(locale: Locale): String { + val languageLocale = if (exceptionalLocaleDisplayedInRootLocale.containsKey(locale.toLanguageTag())) + locale + else + locale.language.constructLocale() + return getSubtypeLocaleDisplayNameInternal(languageLocale, getDisplayLocaleOfSubtypeLocale(locale)) + } + + private fun getSubtypeLocaleDisplayNameInternal(locale: Locale, displayLocale: Locale): String { + val languageTag = locale.toLanguageTag() + if (languageTag == NO_LANGUAGE) { + // "No language" subtype should be displayed in system locale. + return resources.getString(R.string.subtype_no_language) + } + val exceptionalNameResId = if (displayLocale == Locale.ROOT + && exceptionalLocaleDisplayedInRootLocale.containsKey(languageTag) + ) + exceptionalLocaleDisplayedInRootLocale[languageTag] + else + exceptionalLocaleToNameIds[languageTag] + val displayName = if (exceptionalNameResId != null) { + runInLocale(resources, displayLocale) { res: Resources -> res.getString(exceptionalNameResId) } + } else { + getLocaleDisplayNameInLocale(locale, resources, 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 fun getReplacementString(subtype: InputMethodSubtype, displayLocale: Locale): String = + subtype.getExtraValueOf(ExtraValue.UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME) + ?: getSubtypeLocaleDisplayNameInternal(subtype.locale(), displayLocale) + + fun getDisplayNameInSystemLocale(mainLayoutName: String, locale: Locale): String { + getMainLayoutDisplayName(mainLayoutName)?.let { return it } // works for custom and latin layouts + + // we have some locale-specific layout + for (subtype in getResourceSubtypesForLocale(locale)) { + if (mainLayoutName == getMainLayoutFromExtraValue(subtype.extraValue)) + return getSubtypeDisplayNameInSystemLocale(subtype) + } + return mainLayoutName // should never happen... + } + + fun getSubtypeDisplayNameInSystemLocale(subtype: InputMethodSubtype): String { + resourceSubtypeDisplayNames[subtype.hashCode()]?.let { return it } + + val displayName = getSubtypeDisplayNameInternal(subtype, resources.configuration.locale()) + if (!subtype.containsExtraValueKey(ExtraValue.IS_ADDITIONAL_SUBTYPE)) { + resourceSubtypeDisplayNames[subtype.hashCode()] = displayName + } + return displayName + } + + @JvmStatic + fun clearDisplayNameCache() { + resourceSubtypeDisplayNames.clear() + } + + @JvmStatic + fun getSubtypeNameForLogging(subtype: InputMethodSubtype?): String { + if (subtype == null) { + return "" + } + return subtype.locale().toString() + "/" + getMainLayoutName(subtype) + } + + private fun getSubtypeDisplayNameInternal(subtype: InputMethodSubtype, displayLocale: Locale): String { + val replacementString = getReplacementString(subtype, displayLocale) + return runInLocale(resources, displayLocale) { res: Resources -> + try { + StringUtils.capitalizeFirstCodePoint(res.getString(subtype.nameResId, replacementString), displayLocale) + } catch (e: Resources.NotFoundException) { + Log.w(TAG, ("Unknown subtype: mode=${subtype.mode} nameResId=${subtype.nameResId} locale=${subtype.locale()} extra=${subtype.extraValue}\n${DebugLogUtils.getStackTrace()}")) + "" + } + } + } + + fun getMainLayoutDisplayName(subtype: InputMethodSubtype): String? = + getMainLayoutDisplayName(getMainLayoutName(subtype)) + + fun getMainLayoutDisplayName(layoutName: String): String? = + if (isCustomLayout(layoutName)) getDisplayName(layoutName) + else keyboardLayoutToDisplayName[layoutName] + + @JvmStatic + fun getMainLayoutName(subtype: InputMethodSubtype): String { + subtype.mainLayoutName()?.let { return it } + if (!subtype.isAsciiCapable) + Log.w(TAG, "KeyboardLayoutSet not found, use QWERTY: locale=${subtype.locale()} extraValue=${subtype.extraValue}") + return QWERTY + } + + @JvmStatic + fun getCombiningRulesExtraValue(subtype: InputMethodSubtype): String = subtype.getExtraValueOf(ExtraValue.COMBINING_RULES) + + // Special language code to represent "no language". + const val NO_LANGUAGE = "zz" + const val QWERTY = "qwerty" + const val EMOJI = "emoji" + val UNKNOWN_KEYBOARD_LAYOUT = R.string.subtype_generic + + private val TAG = SubtypeLocaleUtils::class.java.simpleName + private const val SUBTYPE_NAME_RESOURCE_PREFIX = "string/subtype_" + private const val SUBTYPE_NAME_RESOURCE_GENERIC_PREFIX = "string/subtype_generic_" + private const val SUBTYPE_NAME_RESOURCE_WITH_LAYOUT_PREFIX = "string/subtype_with_layout_" + private const val SUBTYPE_NAME_RESOURCE_NO_LANGUAGE_PREFIX = "string/subtype_no_language_" + private const val SUBTYPE_NAME_RESOURCE_IN_ROOT_LOCALE_PREFIX = "string/subtype_in_root_locale_" +}