From 42f4561422ae6a6d9b0a419560dcdbc63e57b16e Mon Sep 17 00:00:00 2001 From: Helium314 Date: Sat, 15 Feb 2025 12:22:53 +0100 Subject: [PATCH] convert additionalSubtypeUtils to kotlin --- .../keyboard/keyboard/KeyboardSwitcher.java | 5 +- .../latin/RichInputMethodManager.java | 2 +- .../keyboard/latin/RichInputMethodSubtype.kt | 4 +- .../settings/AdvancedSettingsFragment.kt | 6 +- .../keyboard/latin/settings/Defaults.kt | 6 +- .../latin/settings/LanguageFilterList.kt | 2 +- .../latin/settings/LanguageSettingsDialog.kt | 18 +- .../keyboard/latin/settings/Settings.java | 4 +- .../AndroidSpellCheckerService.java | 4 +- .../latin/utils/AdditionalSubtypeUtils.java | 235 ------------------ .../latin/utils/LanguageOnSpacebarUtils.java | 2 +- .../latin/utils/SubtypeLocaleUtils.java | 24 +- .../keyboard/latin/utils/SubtypeSettings.kt | 39 +-- .../keyboard/latin/utils/SubtypeUtils.kt | 2 +- .../latin/utils/SubtypeUtilsAdditional.kt | 231 +++++++++++++++++ .../preferences/BackupRestorePreference.kt | 6 +- .../helium314/keyboard/KeyboardParserTest.kt | 10 +- 17 files changed, 290 insertions(+), 310 deletions(-) delete mode 100644 app/src/main/java/helium314/keyboard/latin/utils/AdditionalSubtypeUtils.java create mode 100644 app/src/main/java/helium314/keyboard/latin/utils/SubtypeUtilsAdditional.kt diff --git a/app/src/main/java/helium314/keyboard/keyboard/KeyboardSwitcher.java b/app/src/main/java/helium314/keyboard/keyboard/KeyboardSwitcher.java index 5fbe21164..8da04ab47 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/KeyboardSwitcher.java +++ b/app/src/main/java/helium314/keyboard/keyboard/KeyboardSwitcher.java @@ -42,7 +42,6 @@ import helium314.keyboard.latin.WordComposer; import helium314.keyboard.latin.settings.Settings; import helium314.keyboard.latin.settings.SettingsValues; import helium314.keyboard.latin.suggestions.SuggestionStripView; -import helium314.keyboard.latin.utils.AdditionalSubtypeUtils; import helium314.keyboard.latin.utils.CapsModeUtils; import helium314.keyboard.latin.utils.KtxKt; import helium314.keyboard.latin.utils.LanguageOnSpacebarUtils; @@ -50,6 +49,7 @@ import helium314.keyboard.latin.utils.Log; import helium314.keyboard.latin.utils.RecapitalizeStatus; import helium314.keyboard.latin.utils.ResourceUtils; import helium314.keyboard.latin.utils.ScriptUtils; +import helium314.keyboard.latin.utils.SubtypeUtilsAdditional; public final class KeyboardSwitcher implements KeyboardState.SwitchActions { private static final String TAG = KeyboardSwitcher.class.getSimpleName(); @@ -159,7 +159,8 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions { } catch (KeyboardLayoutSetException e) { Log.e(TAG, "loading keyboard failed: " + e.mKeyboardId, e.getCause()); try { - final InputMethodSubtype qwerty = AdditionalSubtypeUtils.createEmojiCapableAdditionalSubtype(mRichImm.getCurrentSubtypeLocale(), "qwerty", true); + final InputMethodSubtype qwerty = SubtypeUtilsAdditional.INSTANCE + .createEmojiCapableAdditionalSubtype(mRichImm.getCurrentSubtypeLocale(), "qwerty", true); mKeyboardLayoutSet = builder.setKeyboardGeometry(keyboardWidth, keyboardHeight) .setSubtype(RichInputMethodSubtype.Companion.get(qwerty)) .setVoiceInputKeyEnabled(settingsValues.mShowsVoiceInputKey) diff --git a/app/src/main/java/helium314/keyboard/latin/RichInputMethodManager.java b/app/src/main/java/helium314/keyboard/latin/RichInputMethodManager.java index eac00a275..ad28f8255 100644 --- a/app/src/main/java/helium314/keyboard/latin/RichInputMethodManager.java +++ b/app/src/main/java/helium314/keyboard/latin/RichInputMethodManager.java @@ -302,7 +302,7 @@ public class RichInputMethodManager { final int count = myImi.getSubtypeCount(); for (int i = 0; i < count; i++) { final InputMethodSubtype subtype = myImi.getSubtypeAt(i); - final String layoutName = SubtypeLocaleUtils.getKeyboardLayoutSetName(subtype); + final String layoutName = SubtypeLocaleUtils.getMainLayoutName(subtype); if (locale.equals(SubtypeUtilsKt.locale(subtype)) && keyboardLayoutSetName.equals(layoutName)) { return subtype; diff --git a/app/src/main/java/helium314/keyboard/latin/RichInputMethodSubtype.kt b/app/src/main/java/helium314/keyboard/latin/RichInputMethodSubtype.kt index 5f5627ae5..5764bd2d5 100644 --- a/app/src/main/java/helium314/keyboard/latin/RichInputMethodSubtype.kt +++ b/app/src/main/java/helium314/keyboard/latin/RichInputMethodSubtype.kt @@ -42,7 +42,7 @@ class RichInputMethodSubtype private constructor(val rawSubtype: InputMethodSubt val fullDisplayName: String get() { if (isNoLanguage) { - return SubtypeLocaleUtils.getKeyboardLayoutSetDisplayName(rawSubtype)!! + return SubtypeLocaleUtils.getMainLayoutDisplayName(rawSubtype)!! } return SubtypeLocaleUtils.getSubtypeLocaleDisplayName(locale) } @@ -51,7 +51,7 @@ class RichInputMethodSubtype private constructor(val rawSubtype: InputMethodSubt // Get the RichInputMethodSubtype's middle display name in its locale. get() { if (isNoLanguage) { - return SubtypeLocaleUtils.getKeyboardLayoutSetDisplayName(rawSubtype)!! + return SubtypeLocaleUtils.getMainLayoutDisplayName(rawSubtype)!! } return SubtypeLocaleUtils.getSubtypeLanguageDisplayName(locale) } diff --git a/app/src/main/java/helium314/keyboard/latin/settings/AdvancedSettingsFragment.kt b/app/src/main/java/helium314/keyboard/latin/settings/AdvancedSettingsFragment.kt index 2dc6cbbc9..99b3b373e 100644 --- a/app/src/main/java/helium314/keyboard/latin/settings/AdvancedSettingsFragment.kt +++ b/app/src/main/java/helium314/keyboard/latin/settings/AdvancedSettingsFragment.kt @@ -45,11 +45,11 @@ import helium314.keyboard.latin.common.FileUtils import helium314.keyboard.latin.common.LocaleUtils.constructLocale import helium314.keyboard.latin.common.splitOnWhitespace import helium314.keyboard.latin.settings.SeekBarDialogPreference.ValueProxy -import helium314.keyboard.latin.utils.AdditionalSubtypeUtils import helium314.keyboard.latin.utils.DeviceProtectedUtils import helium314.keyboard.latin.utils.ExecutorUtils import helium314.keyboard.latin.utils.JniUtils import helium314.keyboard.latin.utils.ResourceUtils +import helium314.keyboard.latin.utils.SubtypeUtilsAdditional import helium314.keyboard.latin.utils.infoDialog import helium314.keyboard.latin.utils.reloadEnabledSubtypes import helium314.keyboard.latin.utils.updateAdditionalSubtypes @@ -409,8 +409,8 @@ class AdvancedSettingsFragment : SubScreenFragment() { } checkVersionUpgrade(requireContext()) Settings.getInstance().startListener() - val additionalSubtypes = sharedPreferences.getString(Settings.PREF_ADDITIONAL_SUBTYPES, Defaults.PREF_ADDITIONAL_SUBTYPES) - updateAdditionalSubtypes(AdditionalSubtypeUtils.createAdditionalSubtypesArray(additionalSubtypes)) + val additionalSubtypes = sharedPreferences.getString(Settings.PREF_ADDITIONAL_SUBTYPES, Defaults.PREF_ADDITIONAL_SUBTYPES)!! + updateAdditionalSubtypes(SubtypeUtilsAdditional.createAdditionalSubtypes(additionalSubtypes)) reloadEnabledSubtypes(requireContext()) val newDictBroadcast = Intent(DictionaryPackConstants.NEW_DICTIONARY_INTENT_ACTION) activity?.sendBroadcast(newDictBroadcast) diff --git a/app/src/main/java/helium314/keyboard/latin/settings/Defaults.kt b/app/src/main/java/helium314/keyboard/latin/settings/Defaults.kt index 9a2079443..c9c57b67a 100644 --- a/app/src/main/java/helium314/keyboard/latin/settings/Defaults.kt +++ b/app/src/main/java/helium314/keyboard/latin/settings/Defaults.kt @@ -7,11 +7,11 @@ import android.util.TypedValue import android.view.Gravity import helium314.keyboard.keyboard.KeyboardTheme import helium314.keyboard.latin.BuildConfig -import helium314.keyboard.latin.utils.AdditionalSubtypeUtils import helium314.keyboard.latin.utils.JniUtils import helium314.keyboard.latin.utils.LayoutType import helium314.keyboard.latin.utils.POPUP_KEYS_LABEL_DEFAULT import helium314.keyboard.latin.utils.POPUP_KEYS_ORDER_DEFAULT +import helium314.keyboard.latin.utils.SubtypeUtilsAdditional import helium314.keyboard.latin.utils.defaultClipboardToolbarPref import helium314.keyboard.latin.utils.defaultPinnedToolbarPref import helium314.keyboard.latin.utils.defaultToolbarPref @@ -73,8 +73,8 @@ object Defaults { const val PREF_LANGUAGE_SWITCH_KEY = "internal" const val PREF_SHOW_EMOJI_KEY = false const val PREF_VARIABLE_TOOLBAR_DIRECTION = true - private const val ls = AdditionalSubtypeUtils.LOCALE_AND_EXTRA_SEPARATOR - private const val subs = AdditionalSubtypeUtils.PREF_SUBTYPE_SEPARATOR + private const val ls = SubtypeUtilsAdditional.LOCALE_AND_EXTRA_SEPARATOR + private const val subs = SubtypeUtilsAdditional.PREF_SUBTYPE_SEPARATOR const val PREF_ADDITIONAL_SUBTYPES = "de${ls}qwerty${ls}AsciiCapable${subs}" + "fr${ls}qwertz:${ls}AsciiCapable${subs}hu${ls}qwerty${ls}AsciiCapable" const val PREF_ENABLE_SPLIT_KEYBOARD = false diff --git a/app/src/main/java/helium314/keyboard/latin/settings/LanguageFilterList.kt b/app/src/main/java/helium314/keyboard/latin/settings/LanguageFilterList.kt index f9560675a..87ca72d1d 100644 --- a/app/src/main/java/helium314/keyboard/latin/settings/LanguageFilterList.kt +++ b/app/src/main/java/helium314/keyboard/latin/settings/LanguageFilterList.kt @@ -89,7 +89,7 @@ private class LanguageAdapter(list: List> = listOf(), c if (infos.size > 1 && !onlySystemLocales) { var start = true infos.forEach { - val string = SpannableString(SubtypeLocaleUtils.getKeyboardLayoutSetDisplayName(it.subtype) + val string = SpannableString(SubtypeLocaleUtils.getMainLayoutDisplayName(it.subtype) ?: it.subtype.displayName(context)) if (it.isEnabled) string.setSpan(StyleSpan(Typeface.BOLD), 0, string.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) diff --git a/app/src/main/java/helium314/keyboard/latin/settings/LanguageSettingsDialog.kt b/app/src/main/java/helium314/keyboard/latin/settings/LanguageSettingsDialog.kt index 4b32bfd55..cd3c57a92 100644 --- a/app/src/main/java/helium314/keyboard/latin/settings/LanguageSettingsDialog.kt +++ b/app/src/main/java/helium314/keyboard/latin/settings/LanguageSettingsDialog.kt @@ -78,8 +78,8 @@ class LanguageSettingsDialog( if (infos.first().subtype.isAsciiCapable) { binding.addSubtype.setOnClickListener { val layouts = context.resources.getStringArray(R.array.predefined_layouts) - .filterNot { layoutName -> infos.any { SubtypeLocaleUtils.getKeyboardLayoutSetName(it.subtype) == layoutName } } - val displayNames = layouts.map { SubtypeLocaleUtils.getKeyboardLayoutSetDisplayName(it) } + .filterNot { layoutName -> infos.any { SubtypeLocaleUtils.getMainLayoutName(it.subtype) == layoutName } } + val displayNames = layouts.map { SubtypeLocaleUtils.getMainLayoutDisplayName(it) } Builder(context) .setTitle(R.string.keyboard_layout_set) .setItems(displayNames.toTypedArray()) { di, i -> @@ -101,17 +101,17 @@ class LanguageSettingsDialog( private fun addSubtype(name: String) { LayoutUtilsCustom.onCustomLayoutFileListChanged() - val newSubtype = AdditionalSubtypeUtils.createEmojiCapableAdditionalSubtype(mainLocale, name, infos.first().subtype.isAsciiCapable) + val newSubtype = SubtypeUtilsAdditional.createEmojiCapableAdditionalSubtype(mainLocale, name, infos.first().subtype.isAsciiCapable) val newSubtypeInfo = newSubtype.toSubtypeInfo(mainLocale, context, true, infos.first().hasDictionary) // enabled by default - val displayName = SubtypeLocaleUtils.getKeyboardLayoutSetDisplayName(newSubtype) - val old = infos.firstOrNull { isAdditionalSubtype(it.subtype) && displayName == SubtypeLocaleUtils.getKeyboardLayoutSetDisplayName(it.subtype) } + val displayName = SubtypeLocaleUtils.getMainLayoutDisplayName(newSubtype) + val old = infos.firstOrNull { isAdditionalSubtype(it.subtype) && displayName == SubtypeLocaleUtils.getMainLayoutDisplayName(it.subtype) } if (old != null) { KeyboardSwitcher.getInstance().forceUpdateKeyboardTheme(context) reloadSetting() return } - addAdditionalSubtype(prefs, newSubtype) + SubtypeUtilsAdditional.addAdditionalSubtype(prefs, newSubtype) addEnabledSubtype(prefs, newSubtype) addSubtypeToView(newSubtypeInfo) KeyboardLayoutSet.onKeyboardThemeChanged() @@ -147,7 +147,7 @@ class LanguageSettingsDialog( if (infos.first().subtype.isAsciiCapable) { context.resources.getStringArray(R.array.predefined_layouts).forEach { layouts.add(it) - displayNames.add(SubtypeLocaleUtils.getKeyboardLayoutSetDisplayName(it) ?: it) + displayNames.add(SubtypeLocaleUtils.getMainLayoutDisplayName(it) ?: it) } } Builder(context) @@ -170,7 +170,7 @@ class LanguageSettingsDialog( val row = LayoutInflater.from(context).inflate(R.layout.language_list_item, listView) val layoutSetName = subtype.subtype.mainLayoutName() ?: "qwerty" row.findViewById(R.id.language_name).text = - SubtypeLocaleUtils.getKeyboardLayoutSetDisplayName(subtype.subtype) + SubtypeLocaleUtils.getMainLayoutDisplayName(subtype.subtype) ?: subtype.subtype.displayName(context) if (LayoutUtilsCustom.isCustomLayout(layoutSetName)) { row.findViewById(R.id.language_details).setText(R.string.edit_layout) @@ -204,7 +204,7 @@ class LanguageSettingsDialog( infos.remove(subtype) if (isCustom) LayoutUtilsCustom.removeCustomLayoutFile(layoutSetName, context) - removeAdditionalSubtype(prefs, subtype.subtype) + SubtypeUtilsAdditional.removeAdditionalSubtype(prefs, subtype.subtype) removeEnabledSubtype(prefs, subtype.subtype) reloadSetting() } diff --git a/app/src/main/java/helium314/keyboard/latin/settings/Settings.java b/app/src/main/java/helium314/keyboard/latin/settings/Settings.java index f4690b525..c53a0dd67 100644 --- a/app/src/main/java/helium314/keyboard/latin/settings/Settings.java +++ b/app/src/main/java/helium314/keyboard/latin/settings/Settings.java @@ -29,7 +29,6 @@ import helium314.keyboard.latin.InputAttributes; import helium314.keyboard.latin.R; import helium314.keyboard.latin.common.Colors; import helium314.keyboard.latin.common.LocaleUtils; -import helium314.keyboard.latin.utils.AdditionalSubtypeUtils; import helium314.keyboard.latin.utils.DeviceProtectedUtils; import helium314.keyboard.latin.utils.KtxKt; import helium314.keyboard.latin.utils.LayoutType; @@ -38,6 +37,7 @@ import helium314.keyboard.latin.utils.ResourceUtils; import helium314.keyboard.latin.utils.RunInLocaleKt; import helium314.keyboard.latin.utils.StatsUtils; import helium314.keyboard.latin.utils.SubtypeSettingsKt; +import helium314.keyboard.latin.utils.SubtypeUtilsAdditional; import helium314.keyboard.latin.utils.ToolbarKey; import helium314.keyboard.latin.utils.ToolbarUtilsKt; import helium314.keyboard.settings.SettingsActivity; @@ -242,7 +242,7 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang } if (PREF_ADDITIONAL_SUBTYPES.equals(key)) { final String additionalSubtypes = prefs.getString(Settings.PREF_ADDITIONAL_SUBTYPES, Defaults.PREF_ADDITIONAL_SUBTYPES); - SubtypeSettingsKt.updateAdditionalSubtypes(AdditionalSubtypeUtils.createAdditionalSubtypesArray(additionalSubtypes)); + SubtypeSettingsKt.updateAdditionalSubtypes(SubtypeUtilsAdditional.INSTANCE.createAdditionalSubtypes(additionalSubtypes)); } } diff --git a/app/src/main/java/helium314/keyboard/latin/spellcheck/AndroidSpellCheckerService.java b/app/src/main/java/helium314/keyboard/latin/spellcheck/AndroidSpellCheckerService.java index db06135cf..28d17f695 100644 --- a/app/src/main/java/helium314/keyboard/latin/spellcheck/AndroidSpellCheckerService.java +++ b/app/src/main/java/helium314/keyboard/latin/spellcheck/AndroidSpellCheckerService.java @@ -30,9 +30,9 @@ import helium314.keyboard.latin.common.ComposedData; import helium314.keyboard.latin.settings.Defaults; import helium314.keyboard.latin.settings.Settings; import helium314.keyboard.latin.settings.SettingsValuesForSuggestion; -import helium314.keyboard.latin.utils.AdditionalSubtypeUtils; import helium314.keyboard.latin.utils.KtxKt; import helium314.keyboard.latin.utils.SubtypeSettingsKt; +import helium314.keyboard.latin.utils.SubtypeUtilsAdditional; import helium314.keyboard.latin.utils.SuggestionResults; import java.util.Locale; @@ -200,7 +200,7 @@ public final class AndroidSpellCheckerService extends SpellCheckerService Settings.getInstance().loadSettings(this, locale, new InputAttributes(editorInfo, false, getPackageName())); } final String keyboardLayoutName = SubtypeSettingsKt.getMatchingLayoutSetNameForLocale(locale); - final InputMethodSubtype subtype = AdditionalSubtypeUtils.createDummyAdditionalSubtype(locale, keyboardLayoutName); + final InputMethodSubtype subtype = SubtypeUtilsAdditional.INSTANCE.createDummyAdditionalSubtype(locale, keyboardLayoutName); final KeyboardLayoutSet keyboardLayoutSet = createKeyboardSetForSpellChecker(subtype); return keyboardLayoutSet.getKeyboard(KeyboardId.ELEMENT_ALPHABET); } diff --git a/app/src/main/java/helium314/keyboard/latin/utils/AdditionalSubtypeUtils.java b/app/src/main/java/helium314/keyboard/latin/utils/AdditionalSubtypeUtils.java deleted file mode 100644 index d32e60c9b..000000000 --- a/app/src/main/java/helium314/keyboard/latin/utils/AdditionalSubtypeUtils.java +++ /dev/null @@ -1,235 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * modified - * SPDX-License-Identifier: Apache-2.0 AND GPL-3.0-only - */ - -package helium314.keyboard.latin.utils; - -import android.os.Build; -import android.text.TextUtils; -import android.view.inputmethod.InputMethodSubtype; - -import helium314.keyboard.latin.R; -import helium314.keyboard.latin.common.LocaleUtils; -import helium314.keyboard.latin.common.StringUtils; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Locale; - -import static helium314.keyboard.latin.common.Constants.Subtype.ExtraValue.ASCII_CAPABLE; -import static helium314.keyboard.latin.common.Constants.Subtype.ExtraValue.EMOJI_CAPABLE; -import static helium314.keyboard.latin.common.Constants.Subtype.ExtraValue.IS_ADDITIONAL_SUBTYPE; -import static helium314.keyboard.latin.common.Constants.Subtype.ExtraValue.KEYBOARD_LAYOUT_SET; -import static helium314.keyboard.latin.common.Constants.Subtype.ExtraValue.UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME; -import static helium314.keyboard.latin.common.Constants.Subtype.KEYBOARD_MODE; - -public final class AdditionalSubtypeUtils { - private static final String TAG = AdditionalSubtypeUtils.class.getSimpleName(); - - private static final InputMethodSubtype[] EMPTY_SUBTYPE_ARRAY = new InputMethodSubtype[0]; - - private AdditionalSubtypeUtils() { - // This utility class is not publicly instantiable. - } - - public static boolean isAdditionalSubtype(final InputMethodSubtype subtype) { - return subtype.containsExtraValueKey(IS_ADDITIONAL_SUBTYPE); - } - - public static final String LOCALE_AND_EXTRA_SEPARATOR = "§"; - private static final int INDEX_OF_LANGUAGE_TAG = 0; - private static final int INDEX_OF_KEYBOARD_LAYOUT = 1; - private static final int INDEX_OF_EXTRA_VALUE = 2; - private static final int LENGTH_WITHOUT_EXTRA_VALUE = (INDEX_OF_KEYBOARD_LAYOUT + 1); - private static final int LENGTH_WITH_EXTRA_VALUE = (INDEX_OF_EXTRA_VALUE + 1); - public static final String PREF_SUBTYPE_SEPARATOR = ";"; - - private static InputMethodSubtype createAdditionalSubtypeInternal( - final Locale locale, final String keyboardLayoutSetName, - final boolean isAsciiCapable, final boolean isEmojiCapable) { - final int nameId = SubtypeLocaleUtils.getSubtypeNameId(locale, keyboardLayoutSetName); - final String platformVersionDependentExtraValues = getPlatformVersionDependentExtraValue( - locale, keyboardLayoutSetName, isAsciiCapable, isEmojiCapable); - final int platformVersionIndependentSubtypeId = - getPlatformVersionIndependentSubtypeId(locale, keyboardLayoutSetName); - final InputMethodSubtype.InputMethodSubtypeBuilder builder = new InputMethodSubtype.InputMethodSubtypeBuilder() - .setSubtypeNameResId(nameId) - .setSubtypeIconResId(R.drawable.ic_ime_switcher) - .setSubtypeLocale(locale.toString()) - .setSubtypeMode(KEYBOARD_MODE) - .setSubtypeExtraValue(platformVersionDependentExtraValues) - .setIsAuxiliary(false) - .setOverridesImplicitlyEnabledSubtype(false) - .setSubtypeId(platformVersionIndependentSubtypeId) - .setIsAsciiCapable(isAsciiCapable); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) - builder.setLanguageTag(locale.toLanguageTag()); - return builder.build(); - } - - public static InputMethodSubtype createDummyAdditionalSubtype( - final Locale locale, final String keyboardLayoutSetName) { - return createAdditionalSubtypeInternal(locale, keyboardLayoutSetName, false, false); - } - - public static InputMethodSubtype createEmojiCapableAdditionalSubtype( - final Locale locale, final String keyboardLayoutSetName, final boolean asciiCapable) { - return createAdditionalSubtypeInternal(locale, keyboardLayoutSetName, asciiCapable, true); - } - - // todo: adjust so we can store more stuff in extra values - private static String getPrefSubtype(final InputMethodSubtype subtype) { - final String keyboardLayoutSetName = SubtypeLocaleUtils.getKeyboardLayoutSetName(subtype); - final String layoutExtraValue = KEYBOARD_LAYOUT_SET + "=MAIN:" + keyboardLayoutSetName; - final String extraValue = StringUtils.removeFromCommaSplittableTextIfExists( - layoutExtraValue, StringUtils.removeFromCommaSplittableTextIfExists( - IS_ADDITIONAL_SUBTYPE, subtype.getExtraValue())); - if (extraValue.contains(PREF_SUBTYPE_SEPARATOR) || extraValue.contains(LOCALE_AND_EXTRA_SEPARATOR)) - throw new IllegalArgumentException("extra value contains not allowed characters " + extraValue); - final String basePrefSubtype = SubtypeUtilsKt.locale(subtype).toLanguageTag() + LOCALE_AND_EXTRA_SEPARATOR - + keyboardLayoutSetName; - return extraValue.isEmpty() ? basePrefSubtype - : basePrefSubtype + LOCALE_AND_EXTRA_SEPARATOR + extraValue; - } - - public static InputMethodSubtype[] createAdditionalSubtypesArray(final String prefSubtypes) { - if (TextUtils.isEmpty(prefSubtypes)) { - return EMPTY_SUBTYPE_ARRAY; - } - final String[] prefSubtypeArray = prefSubtypes.split(PREF_SUBTYPE_SEPARATOR); - final ArrayList subtypesList = new ArrayList<>(prefSubtypeArray.length); - for (final String prefSubtype : prefSubtypeArray) { - final InputMethodSubtype subtype = createSubtypeFromString(prefSubtype); - if (subtype != null) - subtypesList.add(subtype); - } - return subtypesList.toArray(new InputMethodSubtype[0]); - } - - // use string created with getPrefSubtype - public static InputMethodSubtype createSubtypeFromString(final String prefSubtype) { - final String[] elems = prefSubtype.split(LOCALE_AND_EXTRA_SEPARATOR); - if (elems.length != LENGTH_WITHOUT_EXTRA_VALUE - && elems.length != LENGTH_WITH_EXTRA_VALUE) { - Log.w(TAG, "Unknown additional subtype specified: " + prefSubtype); - return null; - } - final String languageTag = elems[INDEX_OF_LANGUAGE_TAG]; - final Locale locale = LocaleUtils.constructLocale(languageTag); - final String keyboardLayoutSetName = elems[INDEX_OF_KEYBOARD_LAYOUT]; - final boolean asciiCapable = ScriptUtils.script(locale).equals(ScriptUtils.SCRIPT_LATIN); - // Here we assume that all the additional subtypes are EmojiCapable - final InputMethodSubtype subtype = createEmojiCapableAdditionalSubtype(locale, keyboardLayoutSetName, asciiCapable); - if (subtype.getNameResId() == SubtypeLocaleUtils.UNKNOWN_KEYBOARD_LAYOUT && !LayoutUtilsCustom.INSTANCE.isCustomLayout(keyboardLayoutSetName)) { - // Skip unknown keyboard layout subtype. This may happen when predefined keyboard - // layout has been removed. - return null; - } - return subtype; - } - - public static String createPrefSubtypes(final InputMethodSubtype[] subtypes) { - if (subtypes == null || subtypes.length == 0) { - return ""; - } - final StringBuilder sb = new StringBuilder(); - for (final InputMethodSubtype subtype : subtypes) { - if (sb.length() > 0) { - sb.append(PREF_SUBTYPE_SEPARATOR); - } - sb.append(getPrefSubtype(subtype)); - } - return sb.toString(); - } - - public static String createPrefSubtypes(final String[] prefSubtypes) { - if (prefSubtypes == null || prefSubtypes.length == 0) { - return ""; - } - final StringBuilder sb = new StringBuilder(); - for (final String prefSubtype : prefSubtypes) { - if (sb.length() > 0) { - sb.append(PREF_SUBTYPE_SEPARATOR); - } - sb.append(prefSubtype); - } - return sb.toString(); - } - - /** - * Returns the extra value that is optimized for the running OS. - *

- * Historically the extra value has been used as the last resort to annotate various kinds of - * attributes. Some of these attributes are valid only on some platform versions. Thus we cannot - * assume that the extra values stored in a persistent storage are always valid. We need to - * regenerate the extra value on the fly instead. - *

- * @param keyboardLayoutSetName the keyboard layout set name (e.g., "dvorak"). - * @param isAsciiCapable true when ASCII characters are supported with this layout. - * @param isEmojiCapable true when Unicode Emoji characters are supported with this layout. - * @return extra value that is optimized for the running OS. - * @see #getPlatformVersionIndependentSubtypeId(Locale, String) - */ - private static String getPlatformVersionDependentExtraValue(final Locale locale, - final String keyboardLayoutSetName, final boolean isAsciiCapable, - final boolean isEmojiCapable) { - final ArrayList extraValueItems = new ArrayList<>(); - extraValueItems.add(KEYBOARD_LAYOUT_SET + "=MAIN:" + keyboardLayoutSetName); - if (isAsciiCapable) { - extraValueItems.add(ASCII_CAPABLE); - } - if (SubtypeLocaleUtils.isExceptionalLocale(locale)) { - extraValueItems.add(UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME + "=" + - SubtypeLocaleUtils.getKeyboardLayoutSetDisplayName(keyboardLayoutSetName)); - } - if (isEmojiCapable) { - extraValueItems.add(EMOJI_CAPABLE); - } - extraValueItems.add(IS_ADDITIONAL_SUBTYPE); - return TextUtils.join(",", extraValueItems); - } - - /** - * Returns the subtype ID that is supposed to be compatible between different version of OSes. - *

- * From the compatibility point of view, it is important to keep subtype id predictable and - * stable between different OSes. For this purpose, the calculation code in this method is - * carefully chosen and then fixed. Treat the following code as no more or less than a - * hash function. Each component to be hashed can be different from the corresponding value - * that is used to instantiate {@link InputMethodSubtype} actually. - * For example, you don't need to update compatibilityExtraValueItems in this - * method even when we need to add some new extra values for the actual instance of - * {@link InputMethodSubtype}. - *

- * @param keyboardLayoutSetName the keyboard layout set name (e.g., "dvorak"). - * @return a platform-version independent subtype ID. - * @see #getPlatformVersionDependentExtraValue(Locale, String, boolean, boolean) - */ - private static int getPlatformVersionIndependentSubtypeId(final Locale locale, - final String keyboardLayoutSetName) { - // For compatibility reasons, we concatenate the extra values in the following order. - // - KeyboardLayoutSet - // - AsciiCapable - // - UntranslatableReplacementStringInSubtypeName - // - EmojiCapable - // - isAdditionalSubtype - final ArrayList compatibilityExtraValueItems = new ArrayList<>(); - compatibilityExtraValueItems.add(KEYBOARD_LAYOUT_SET + "=MAIN:" + keyboardLayoutSetName); - compatibilityExtraValueItems.add(ASCII_CAPABLE); - if (SubtypeLocaleUtils.isExceptionalLocale(locale)) { - compatibilityExtraValueItems.add(UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME + "=" + - SubtypeLocaleUtils.getKeyboardLayoutSetDisplayName(keyboardLayoutSetName)); - } - compatibilityExtraValueItems.add(EMOJI_CAPABLE); - compatibilityExtraValueItems.add(IS_ADDITIONAL_SUBTYPE); - final String compatibilityExtraValues = TextUtils.join(",", compatibilityExtraValueItems); - return Arrays.hashCode(new Object[] { - locale, - KEYBOARD_MODE, - compatibilityExtraValues, - false /* isAuxiliary */, - false /* overrideImplicitlyEnabledSubtype */ }); - } -} diff --git a/app/src/main/java/helium314/keyboard/latin/utils/LanguageOnSpacebarUtils.java b/app/src/main/java/helium314/keyboard/latin/utils/LanguageOnSpacebarUtils.java index efdca7c0b..b5c394560 100644 --- a/app/src/main/java/helium314/keyboard/latin/utils/LanguageOnSpacebarUtils.java +++ b/app/src/main/java/helium314/keyboard/latin/utils/LanguageOnSpacebarUtils.java @@ -52,7 +52,7 @@ public final class LanguageOnSpacebarUtils { for (final InputMethodSubtype ims : sEnabledSubtypes) { final String language = SubtypeUtilsKt.locale(ims).getLanguage(); if (keyboardLanguage.equals(language) && keyboardLayout.equals( - SubtypeLocaleUtils.getKeyboardLayoutSetName(ims))) { + SubtypeLocaleUtils.getMainLayoutName(ims))) { sameLanguageAndLayoutCount++; } } diff --git a/app/src/main/java/helium314/keyboard/latin/utils/SubtypeLocaleUtils.java b/app/src/main/java/helium314/keyboard/latin/utils/SubtypeLocaleUtils.java index 446acff3c..76bd431ed 100644 --- a/app/src/main/java/helium314/keyboard/latin/utils/SubtypeLocaleUtils.java +++ b/app/src/main/java/helium314/keyboard/latin/utils/SubtypeLocaleUtils.java @@ -122,7 +122,7 @@ public final class SubtypeLocaleUtils { return NO_LANGUAGE + "_" + keyboardLayoutName; } - public static int getSubtypeNameId(final Locale locale, final String keyboardLayoutName) { + public static int getSubtypeNameResId(final Locale locale, final String keyboardLayoutName) { final String languageTag = locale.toLanguageTag(); if (isExceptionalLocale(locale)) { return sExceptionalLocaleToWithLayoutNameIdsMap.get(languageTag); @@ -235,7 +235,7 @@ public final class SubtypeLocaleUtils { if (subtype == null) { return ""; } - return SubtypeUtilsKt.locale(subtype) + "/" + getKeyboardLayoutSetName(subtype); + return SubtypeUtilsKt.locale(subtype) + "/" + getMainLayoutName(subtype); } @NonNull @@ -259,30 +259,30 @@ public final class SubtypeLocaleUtils { } @Nullable - public static String getKeyboardLayoutSetDisplayName(@NonNull final InputMethodSubtype subtype) { - final String layoutName = getKeyboardLayoutSetName(subtype); - return getKeyboardLayoutSetDisplayName(layoutName); + public static String getMainLayoutDisplayName(@NonNull final InputMethodSubtype subtype) { + final String layoutName = getMainLayoutName(subtype); + return getMainLayoutDisplayName(layoutName); } @Nullable - public static String getKeyboardLayoutSetDisplayName(@NonNull final String layoutName) { + public static String getMainLayoutDisplayName(@NonNull final String layoutName) { if (LayoutUtilsCustom.INSTANCE.isCustomLayout(layoutName)) return LayoutUtilsCustom.INSTANCE.getCustomLayoutDisplayName(layoutName); return sKeyboardLayoutToDisplayNameMap.get(layoutName); } @NonNull - public static String getKeyboardLayoutSetName(final InputMethodSubtype subtype) { - String keyboardLayoutSet = SubtypeUtilsKt.mainLayoutName(subtype); - if (keyboardLayoutSet == null && subtype.isAsciiCapable()) { - keyboardLayoutSet = QWERTY; + public static String getMainLayoutName(final InputMethodSubtype subtype) { + String mainLayoutName = SubtypeUtilsKt.mainLayoutName(subtype); + if (mainLayoutName == null && subtype.isAsciiCapable()) { + mainLayoutName = QWERTY; } - if (keyboardLayoutSet == null) { // we could search for a subtype with the correct script, but this is a bug anyway... + 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 keyboardLayoutSet; + return mainLayoutName; } public static String getCombiningRulesExtraValue(final InputMethodSubtype subtype) { diff --git a/app/src/main/java/helium314/keyboard/latin/utils/SubtypeSettings.kt b/app/src/main/java/helium314/keyboard/latin/utils/SubtypeSettings.kt index 3c3abea04..f6bac728f 100644 --- a/app/src/main/java/helium314/keyboard/latin/utils/SubtypeSettings.kt +++ b/app/src/main/java/helium314/keyboard/latin/utils/SubtypeSettings.kt @@ -20,9 +20,7 @@ import helium314.keyboard.latin.settings.Defaults import helium314.keyboard.latin.settings.Settings import helium314.keyboard.latin.utils.ScriptUtils.script import org.xmlpull.v1.XmlPullParser -import java.util.* - -// todo: move some parts, to subtypeUtils, and only keep actual settings? +import java.util.Locale /** @return enabled subtypes. If no subtypes are enabled, but a contextForFallback is provided, * subtypes for system locales will be returned, or en-US if none found. */ @@ -78,28 +76,12 @@ fun removeEnabledSubtype(prefs: SharedPreferences, subtype: InputMethodSubtype) RichInputMethodManager.getInstance().refreshSubtypeCaches() } -fun addAdditionalSubtype(prefs: SharedPreferences, subtype: InputMethodSubtype) { - val oldAdditionalSubtypesString = prefs.getString(Settings.PREF_ADDITIONAL_SUBTYPES, Defaults.PREF_ADDITIONAL_SUBTYPES) - val additionalSubtypes = AdditionalSubtypeUtils.createAdditionalSubtypesArray(oldAdditionalSubtypesString).toMutableSet() - additionalSubtypes.add(subtype) - val newAdditionalSubtypesString = AdditionalSubtypeUtils.createPrefSubtypes(additionalSubtypes.toTypedArray()) - Settings.writePrefAdditionalSubtypes(prefs, newAdditionalSubtypesString) -} - -fun removeAdditionalSubtype(prefs: SharedPreferences, subtype: InputMethodSubtype) { - val oldAdditionalSubtypesString = prefs.getString(Settings.PREF_ADDITIONAL_SUBTYPES, Defaults.PREF_ADDITIONAL_SUBTYPES) - val oldAdditionalSubtypes = AdditionalSubtypeUtils.createAdditionalSubtypesArray(oldAdditionalSubtypesString) - val newAdditionalSubtypes = oldAdditionalSubtypes.filter { it != subtype } - val newAdditionalSubtypesString = AdditionalSubtypeUtils.createPrefSubtypes(newAdditionalSubtypes.toTypedArray()) - Settings.writePrefAdditionalSubtypes(prefs, newAdditionalSubtypesString) -} - fun getSelectedSubtype(prefs: SharedPreferences): InputMethodSubtype { require(initialized) val localeAndLayout = prefs.getString(Settings.PREF_SELECTED_SUBTYPE, Defaults.PREF_SELECTED_SUBTYPE)!!.toLocaleAndLayout() val subtypes = if (prefs.getBoolean(Settings.PREF_USE_SYSTEM_LOCALES, Defaults.PREF_USE_SYSTEM_LOCALES)) getDefaultEnabledSubtypes() else enabledSubtypes - val subtype = subtypes.firstOrNull { localeAndLayout.first == it.locale() && localeAndLayout.second == SubtypeLocaleUtils.getKeyboardLayoutSetName(it) } + val subtype = subtypes.firstOrNull { localeAndLayout.first == it.locale() && localeAndLayout.second == SubtypeLocaleUtils.getMainLayoutName(it) } if (subtype != null) { return subtype } else { @@ -108,8 +90,8 @@ fun getSelectedSubtype(prefs: SharedPreferences): InputMethodSubtype { if (subtypes.isNotEmpty()) return subtypes.first() val defaultSubtypes = getDefaultEnabledSubtypes() - return defaultSubtypes.firstOrNull { localeAndLayout.first == it.locale() && localeAndLayout.second == SubtypeLocaleUtils.getKeyboardLayoutSetName(it) } - ?: defaultSubtypes.firstOrNull { localeAndLayout.first.language == it.locale().language && localeAndLayout.second == SubtypeLocaleUtils.getKeyboardLayoutSetName(it) } + return defaultSubtypes.firstOrNull { localeAndLayout.first == it.locale() && localeAndLayout.second == SubtypeLocaleUtils.getMainLayoutName(it) } + ?: defaultSubtypes.firstOrNull { localeAndLayout.first.language == it.locale().language && localeAndLayout.second == SubtypeLocaleUtils.getMainLayoutName(it) } ?: defaultSubtypes.first() } @@ -120,11 +102,12 @@ fun setSelectedSubtype(prefs: SharedPreferences, subtype: InputMethodSubtype) { prefs.edit { putString(Settings.PREF_SELECTED_SUBTYPE, subtypeString) } } +// todo: use this or the version in SubtypeUtilsAdditional? fun isAdditionalSubtype(subtype: InputMethodSubtype): Boolean { return subtype in additionalSubtypes } -fun updateAdditionalSubtypes(subtypes: Array) { +fun updateAdditionalSubtypes(subtypes: List) { additionalSubtypes.clear() additionalSubtypes.addAll(subtypes) RichInputMethodManager.getInstance().refreshSubtypeCaches() @@ -202,7 +185,7 @@ private fun InputMethodSubtype.prefString(): String { @Suppress("deprecation") // it's debug logging, better get all information Log.e(TAG, "unknown language, should not happen ${locale}, $languageTag, $extraValue, ${hashCode()}, $nameResId") } - return locale().toLanguageTag() + LOCALE_LAYOUT_SEPARATOR + SubtypeLocaleUtils.getKeyboardLayoutSetName(this) + return locale().toLanguageTag() + LOCALE_LAYOUT_SEPARATOR + SubtypeLocaleUtils.getMainLayoutName(this) } private fun String.toLocaleAndLayout(): Pair = @@ -263,8 +246,8 @@ private fun removeInvalidCustomSubtypes(context: Context) { // todo: new layout } private fun loadAdditionalSubtypes(prefs: SharedPreferences) { - val additionalSubtypeString = prefs.getString(Settings.PREF_ADDITIONAL_SUBTYPES, Defaults.PREF_ADDITIONAL_SUBTYPES) - val subtypes = AdditionalSubtypeUtils.createAdditionalSubtypesArray(additionalSubtypeString) + val additionalSubtypeString = prefs.getString(Settings.PREF_ADDITIONAL_SUBTYPES, Defaults.PREF_ADDITIONAL_SUBTYPES)!! + val subtypes = SubtypeUtilsAdditional.createAdditionalSubtypes(additionalSubtypeString) additionalSubtypes.addAll(subtypes) } @@ -286,8 +269,8 @@ private fun loadEnabledSubtypes(context: Context) { continue } - val subtype = subtypesForLocale.firstOrNull { SubtypeLocaleUtils.getKeyboardLayoutSetName(it) == localeAndLayout.second } - ?: additionalSubtypes.firstOrNull { it.locale() == localeAndLayout.first && SubtypeLocaleUtils.getKeyboardLayoutSetName(it) == localeAndLayout.second } + val subtype = subtypesForLocale.firstOrNull { SubtypeLocaleUtils.getMainLayoutName(it) == localeAndLayout.second } + ?: additionalSubtypes.firstOrNull { it.locale() == localeAndLayout.first && SubtypeLocaleUtils.getMainLayoutName(it) == localeAndLayout.second } if (subtype == null) { val message = "subtype $localeAndLayout could not be loaded" Log.w(TAG, message) diff --git a/app/src/main/java/helium314/keyboard/latin/utils/SubtypeUtils.kt b/app/src/main/java/helium314/keyboard/latin/utils/SubtypeUtils.kt index acf57734a..a218824e3 100644 --- a/app/src/main/java/helium314/keyboard/latin/utils/SubtypeUtils.kt +++ b/app/src/main/java/helium314/keyboard/latin/utils/SubtypeUtils.kt @@ -24,7 +24,7 @@ fun InputMethodSubtype.mainLayoutName(): String? { /** Workaround for SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale ignoring custom layout names */ // todo (later): this should be done properly and in SubtypeLocaleUtils fun InputMethodSubtype.displayName(context: Context): CharSequence { - val layoutName = SubtypeLocaleUtils.getKeyboardLayoutSetName(this) + val layoutName = SubtypeLocaleUtils.getMainLayoutName(this) if (LayoutUtilsCustom.isCustomLayout(layoutName)) return "${LocaleUtils.getLocaleDisplayNameInSystemLocale(locale(), context)} (${LayoutUtilsCustom.getCustomLayoutDisplayName(layoutName)})" return SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(this) diff --git a/app/src/main/java/helium314/keyboard/latin/utils/SubtypeUtilsAdditional.kt b/app/src/main/java/helium314/keyboard/latin/utils/SubtypeUtilsAdditional.kt new file mode 100644 index 000000000..127a70e30 --- /dev/null +++ b/app/src/main/java/helium314/keyboard/latin/utils/SubtypeUtilsAdditional.kt @@ -0,0 +1,231 @@ +package helium314.keyboard.latin.utils + +import android.content.SharedPreferences +import android.os.Build +import android.text.TextUtils +import android.view.inputmethod.InputMethodSubtype +import android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder +import helium314.keyboard.latin.R +import helium314.keyboard.latin.common.Constants +import helium314.keyboard.latin.common.Constants.Subtype.ExtraValue +import helium314.keyboard.latin.common.LocaleUtils.constructLocale +import helium314.keyboard.latin.common.StringUtils +import helium314.keyboard.latin.settings.Defaults +import helium314.keyboard.latin.settings.Settings +import helium314.keyboard.latin.utils.LayoutUtilsCustom.isCustomLayout +import helium314.keyboard.latin.utils.ScriptUtils.script +import java.util.Locale + +object SubtypeUtilsAdditional { + fun isAdditionalSubtype(subtype: InputMethodSubtype): Boolean { + return subtype.containsExtraValueKey(ExtraValue.IS_ADDITIONAL_SUBTYPE) + } + + private fun createAdditionalSubtypeInternal(locale: Locale, keyboardLayoutSetName: String, + isAsciiCapable: Boolean, isEmojiCapable: Boolean + ): InputMethodSubtype { + val nameId = SubtypeLocaleUtils.getSubtypeNameResId(locale, keyboardLayoutSetName) + val platformVersionDependentExtraValues = + getPlatformVersionDependentExtraValue(locale, keyboardLayoutSetName, isAsciiCapable, isEmojiCapable) + val platformVersionIndependentSubtypeId = + getPlatformVersionIndependentSubtypeId(locale, keyboardLayoutSetName) + val builder = InputMethodSubtypeBuilder() + .setSubtypeNameResId(nameId) + .setSubtypeIconResId(R.drawable.ic_ime_switcher) + .setSubtypeLocale(locale.toString()) + .setSubtypeMode(Constants.Subtype.KEYBOARD_MODE) + .setSubtypeExtraValue(platformVersionDependentExtraValues) + .setIsAuxiliary(false) + .setOverridesImplicitlyEnabledSubtype(false) + .setSubtypeId(platformVersionIndependentSubtypeId) + .setIsAsciiCapable(isAsciiCapable) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) + builder.setLanguageTag(locale.toLanguageTag()) + return builder.build() + } + + fun createDummyAdditionalSubtype(locale: Locale, keyboardLayoutSetName: String) = + createAdditionalSubtypeInternal(locale, keyboardLayoutSetName, false, false) + + fun createEmojiCapableAdditionalSubtype(locale: Locale, keyboardLayoutSetName: String, asciiCapable: Boolean) = + createAdditionalSubtypeInternal(locale, keyboardLayoutSetName, asciiCapable, true) + + fun addAdditionalSubtype(prefs: SharedPreferences, subtype: InputMethodSubtype) { + val oldAdditionalSubtypesString = prefs.getString(Settings.PREF_ADDITIONAL_SUBTYPES, Defaults.PREF_ADDITIONAL_SUBTYPES)!! + val additionalSubtypes = createAdditionalSubtypes(oldAdditionalSubtypesString).toMutableSet() + additionalSubtypes.add(subtype) + val newAdditionalSubtypesString = createPrefSubtypes(additionalSubtypes.toTypedArray()) + Settings.writePrefAdditionalSubtypes(prefs, newAdditionalSubtypesString) + } + + fun removeAdditionalSubtype(prefs: SharedPreferences, subtype: InputMethodSubtype) { + val oldAdditionalSubtypesString = prefs.getString(Settings.PREF_ADDITIONAL_SUBTYPES, Defaults.PREF_ADDITIONAL_SUBTYPES)!! + val oldAdditionalSubtypes = createAdditionalSubtypes(oldAdditionalSubtypesString) + val newAdditionalSubtypes = oldAdditionalSubtypes.filter { it != subtype } + val newAdditionalSubtypesString = createPrefSubtypes(newAdditionalSubtypes.toTypedArray()) + Settings.writePrefAdditionalSubtypes(prefs, newAdditionalSubtypesString) + } + + // todo: adjust so we can store more stuff in extra values + private fun getPrefSubtype(subtype: InputMethodSubtype): String { + val mainLayoutName = SubtypeLocaleUtils.getMainLayoutName(subtype) + val layoutExtraValue = ExtraValue.KEYBOARD_LAYOUT_SET + "=MAIN:" + mainLayoutName + val extraValue = StringUtils.removeFromCommaSplittableTextIfExists( + layoutExtraValue, StringUtils.removeFromCommaSplittableTextIfExists( + ExtraValue.IS_ADDITIONAL_SUBTYPE, subtype.extraValue + ) + ) + require(!extraValue.contains(PREF_SUBTYPE_SEPARATOR) && !extraValue.contains(LOCALE_AND_EXTRA_SEPARATOR)) + { "extra value contains not allowed characters $extraValue" } + val basePrefSubtype = subtype.locale().toLanguageTag() + LOCALE_AND_EXTRA_SEPARATOR + mainLayoutName + return if (extraValue.isEmpty()) basePrefSubtype + else basePrefSubtype + LOCALE_AND_EXTRA_SEPARATOR + extraValue + } + + fun createAdditionalSubtypes(prefSubtypes: String): List { + if (TextUtils.isEmpty(prefSubtypes)) { + return emptyList() + } + return prefSubtypes.split(PREF_SUBTYPE_SEPARATOR) + .mapNotNull { createSubtypeFromString(it) } + } + + // use string created with getPrefSubtype + fun createSubtypeFromString(prefSubtype: String): InputMethodSubtype? { + val elems = prefSubtype.split(LOCALE_AND_EXTRA_SEPARATOR) + if (elems.size != LENGTH_WITHOUT_EXTRA_VALUE && elems.size != LENGTH_WITH_EXTRA_VALUE) { + Log.w(TAG, "Unknown additional subtype specified: $prefSubtype") + return null + } + val languageTag = elems[INDEX_OF_LANGUAGE_TAG] + val locale = languageTag.constructLocale() + val keyboardLayoutSet = elems[INDEX_OF_KEYBOARD_LAYOUT] + val asciiCapable = locale.script() == ScriptUtils.SCRIPT_LATIN + // Here we assume that all the additional subtypes are EmojiCapable + val subtype = createEmojiCapableAdditionalSubtype(locale, keyboardLayoutSet, asciiCapable) + if (subtype.nameResId == SubtypeLocaleUtils.UNKNOWN_KEYBOARD_LAYOUT && !isCustomLayout(keyboardLayoutSet)) { + // Skip unknown keyboard layout subtype. This may happen when predefined keyboard + // layout has been removed. + return null + } + return subtype + } + + fun createPrefSubtypes(subtypes: Array?): String { + if (subtypes.isNullOrEmpty()) { + return "" + } + val sb = StringBuilder() + for (subtype in subtypes) { + if (sb.isNotEmpty()) { + sb.append(PREF_SUBTYPE_SEPARATOR) + } + sb.append(getPrefSubtype(subtype)) + } + return sb.toString() + } + + fun createPrefSubtypes(prefSubtypes: Array?): String { + if (prefSubtypes.isNullOrEmpty()) { + return "" + } + val sb = StringBuilder() + for (prefSubtype in prefSubtypes) { + if (sb.isNotEmpty()) { + sb.append(PREF_SUBTYPE_SEPARATOR) + } + sb.append(prefSubtype) + } + return sb.toString() + } + + /** + * Returns the extra value that is optimized for the running OS. + * + * + * Historically the extra value has been used as the last resort to annotate various kinds of + * attributes. Some of these attributes are valid only on some platform versions. Thus we cannot + * assume that the extra values stored in a persistent storage are always valid. We need to + * regenerate the extra value on the fly instead. + * + * @param keyboardLayoutSetName the keyboard layout set name (e.g., "dvorak"). + * @param isAsciiCapable true when ASCII characters are supported with this layout. + * @param isEmojiCapable true when Unicode Emoji characters are supported with this layout. + * @return extra value that is optimized for the running OS. + * @see .getPlatformVersionIndependentSubtypeId + */ + private fun getPlatformVersionDependentExtraValue(locale: Locale, + keyboardLayoutSetName: String, isAsciiCapable: Boolean, isEmojiCapable: Boolean + ): String { + val extraValueItems = mutableListOf() + extraValueItems.add(ExtraValue.KEYBOARD_LAYOUT_SET + "=MAIN:" + keyboardLayoutSetName) + if (isAsciiCapable) { + extraValueItems.add(ExtraValue.ASCII_CAPABLE) + } + if (SubtypeLocaleUtils.isExceptionalLocale(locale)) { + extraValueItems.add( + ExtraValue.UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME + "=" + + SubtypeLocaleUtils.getMainLayoutDisplayName(keyboardLayoutSetName) + ) + } + if (isEmojiCapable) { + extraValueItems.add(ExtraValue.EMOJI_CAPABLE) + } + extraValueItems.add(ExtraValue.IS_ADDITIONAL_SUBTYPE) + return extraValueItems.joinToString(",") + } + + /** + * Returns the subtype ID that is supposed to be compatible between different version of OSes. + * + * + * From the compatibility point of view, it is important to keep subtype id predictable and + * stable between different OSes. For this purpose, the calculation code in this method is + * carefully chosen and then fixed. Treat the following code as no more or less than a + * hash function. Each component to be hashed can be different from the corresponding value + * that is used to instantiate [InputMethodSubtype] actually. + * For example, you don't need to update `compatibilityExtraValueItems` in this + * method even when we need to add some new extra values for the actual instance of + * [InputMethodSubtype]. + * + * @param keyboardLayoutSetName the keyboard layout set name (e.g., "dvorak"). + * @return a platform-version independent subtype ID. + * @see .getPlatformVersionDependentExtraValue + */ + private fun getPlatformVersionIndependentSubtypeId(locale: Locale, keyboardLayoutSetName: String): Int { + // For compatibility reasons, we concatenate the extra values in the following order. + // - KeyboardLayoutSet + // - AsciiCapable + // - UntranslatableReplacementStringInSubtypeName + // - EmojiCapable + // - isAdditionalSubtype + val compatibilityExtraValueItems = mutableListOf() + compatibilityExtraValueItems.add(ExtraValue.KEYBOARD_LAYOUT_SET + "=MAIN:" + keyboardLayoutSetName) + compatibilityExtraValueItems.add(ExtraValue.ASCII_CAPABLE) + if (SubtypeLocaleUtils.isExceptionalLocale(locale)) { + compatibilityExtraValueItems.add( + ExtraValue.UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME + "=" + + SubtypeLocaleUtils.getMainLayoutDisplayName(keyboardLayoutSetName) + ) + } + compatibilityExtraValueItems.add(ExtraValue.EMOJI_CAPABLE) + compatibilityExtraValueItems.add(ExtraValue.IS_ADDITIONAL_SUBTYPE) + val compatibilityExtraValues = compatibilityExtraValueItems.joinToString(",") + return arrayOf( + locale, + Constants.Subtype.KEYBOARD_MODE, + compatibilityExtraValues, + false, // isAuxiliary + false // overrideImplicitlyEnabledSubtype + ).contentHashCode() + } + + private val TAG: String = SubtypeUtilsAdditional::class.java.simpleName + const val LOCALE_AND_EXTRA_SEPARATOR: String = "§" + const val PREF_SUBTYPE_SEPARATOR: String = ";" + private const val INDEX_OF_LANGUAGE_TAG: Int = 0 + private const val INDEX_OF_KEYBOARD_LAYOUT: Int = 1 + private const val INDEX_OF_EXTRA_VALUE: Int = 2 + private const val LENGTH_WITHOUT_EXTRA_VALUE: Int = (INDEX_OF_KEYBOARD_LAYOUT + 1) + private const val LENGTH_WITH_EXTRA_VALUE: Int = (INDEX_OF_EXTRA_VALUE + 1) +} diff --git a/app/src/main/java/helium314/keyboard/settings/preferences/BackupRestorePreference.kt b/app/src/main/java/helium314/keyboard/settings/preferences/BackupRestorePreference.kt index 3e151c174..27dd2ca84 100644 --- a/app/src/main/java/helium314/keyboard/settings/preferences/BackupRestorePreference.kt +++ b/app/src/main/java/helium314/keyboard/settings/preferences/BackupRestorePreference.kt @@ -30,11 +30,11 @@ import helium314.keyboard.latin.common.LocaleUtils.constructLocale import helium314.keyboard.latin.settings.Defaults import helium314.keyboard.latin.settings.Settings import helium314.keyboard.latin.settings.USER_DICTIONARY_SUFFIX -import helium314.keyboard.latin.utils.AdditionalSubtypeUtils import helium314.keyboard.latin.utils.DeviceProtectedUtils import helium314.keyboard.latin.utils.ExecutorUtils import helium314.keyboard.latin.utils.LayoutUtilsCustom import helium314.keyboard.latin.utils.Log +import helium314.keyboard.latin.utils.SubtypeUtilsAdditional import helium314.keyboard.latin.utils.getActivity import helium314.keyboard.latin.utils.prefs import helium314.keyboard.latin.utils.protectedPrefs @@ -175,8 +175,8 @@ fun BackupRestorePreference(setting: Setting) { wait.await() checkVersionUpgrade(ctx) Settings.getInstance().startListener() - val additionalSubtypes = prefs.getString(Settings.PREF_ADDITIONAL_SUBTYPES, Defaults.PREF_ADDITIONAL_SUBTYPES) - updateAdditionalSubtypes(AdditionalSubtypeUtils.createAdditionalSubtypesArray(additionalSubtypes)) + val additionalSubtypes = prefs.getString(Settings.PREF_ADDITIONAL_SUBTYPES, Defaults.PREF_ADDITIONAL_SUBTYPES)!! + updateAdditionalSubtypes(SubtypeUtilsAdditional.createAdditionalSubtypes(additionalSubtypes)) reloadEnabledSubtypes(ctx) val newDictBroadcast = Intent(DictionaryPackConstants.NEW_DICTIONARY_INTENT_ACTION) ctx.getActivity()?.sendBroadcast(newDictBroadcast) diff --git a/app/src/test/java/helium314/keyboard/KeyboardParserTest.kt b/app/src/test/java/helium314/keyboard/KeyboardParserTest.kt index c3a05831e..5657b2584 100644 --- a/app/src/test/java/helium314/keyboard/KeyboardParserTest.kt +++ b/app/src/test/java/helium314/keyboard/KeyboardParserTest.kt @@ -19,9 +19,9 @@ import helium314.keyboard.keyboard.internal.keyboard_parser.addLocaleKeyTextsToP import helium314.keyboard.keyboard.internal.keyboard_parser.floris.KeyCode import helium314.keyboard.latin.LatinIME import helium314.keyboard.latin.RichInputMethodSubtype -import helium314.keyboard.latin.utils.AdditionalSubtypeUtils.createEmojiCapableAdditionalSubtype import helium314.keyboard.latin.utils.LayoutUtilsCustom import helium314.keyboard.latin.utils.POPUP_KEYS_LAYOUT +import helium314.keyboard.latin.utils.SubtypeUtilsAdditional import org.junit.runner.RunWith import org.robolectric.Robolectric import org.robolectric.RobolectricTestRunner @@ -418,21 +418,21 @@ f""", // no newline at the end @Test fun canLoadKeyboard() { val editorInfo = EditorInfo() - val subtype = createEmojiCapableAdditionalSubtype(Locale.ENGLISH, "qwerty", true) + val subtype = SubtypeUtilsAdditional.createEmojiCapableAdditionalSubtype(Locale.ENGLISH, "qwerty", true) val (kb, keys) = buildKeyboard(editorInfo, subtype, KeyboardId.ELEMENT_ALPHABET) assertEquals(kb.sortedKeys.size, keys.sumOf { it.size }) } @Test fun `dvorak has 4 rows`() { val editorInfo = EditorInfo() - val subtype = createEmojiCapableAdditionalSubtype(Locale.ENGLISH, "dvorak", true) + val subtype = SubtypeUtilsAdditional.createEmojiCapableAdditionalSubtype(Locale.ENGLISH, "dvorak", true) val (kb, keys) = buildKeyboard(editorInfo, subtype, KeyboardId.ELEMENT_ALPHABET) assertEquals(keys.size, 4) } @Test fun `de_DE has extra keys`() { val editorInfo = EditorInfo() - val subtype = createEmojiCapableAdditionalSubtype(Locale.GERMANY, "qwertz+", true) + val subtype = SubtypeUtilsAdditional.createEmojiCapableAdditionalSubtype(Locale.GERMANY, "qwertz+", true) val (kb, keys) = buildKeyboard(editorInfo, subtype, KeyboardId.ELEMENT_ALPHABET) assertEquals(11, keys[0].size) assertEquals(11, keys[1].size) @@ -445,7 +445,7 @@ f""", // no newline at the end @Test fun `popup key count does not depend on shift for (for simple layout)`() { val editorInfo = EditorInfo() - val subtype = createEmojiCapableAdditionalSubtype(Locale.ENGLISH, "qwerty", true) + val subtype = SubtypeUtilsAdditional.createEmojiCapableAdditionalSubtype(Locale.ENGLISH, "qwerty", true) val (kb, keys) = buildKeyboard(editorInfo, subtype, KeyboardId.ELEMENT_ALPHABET) val (kb2, keys2) = buildKeyboard(editorInfo, subtype, KeyboardId.ELEMENT_ALPHABET_AUTOMATIC_SHIFTED) assertEquals(kb.sortedKeys.size, kb2.sortedKeys.size)