change what can be stored in KeyboardLayoutSet subtype extra value

and some more preparations for adjustments related to language/layout settings upgrade
This commit is contained in:
Helium314 2025-02-13 17:29:51 +01:00
parent f2f7426ee5
commit 201b430362
25 changed files with 313 additions and 371 deletions

View file

@ -11,7 +11,7 @@ object HangulEventDecoder {
@JvmStatic
fun decodeHardwareKeyEvent(subtype: RichInputMethodSubtype, event: KeyEvent, defaultEvent: () -> Event): Event {
val layout = LAYOUTS[subtype.keyboardLayoutSetName] ?: return defaultEvent()
val layout = LAYOUTS[subtype.mainLayoutName] ?: return defaultEvent()
val codePoint = layout[event.keyCode]?.let { if (event.isShiftPressed) it.second else it.first } ?: return defaultEvent()
val hardwareEvent = Event.createHardwareKeypressEvent(codePoint, event.keyCode, event.metaState, null, event.repeatCount != 0)
return decodeSoftwareKeyEvent(hardwareEvent)

View file

@ -236,7 +236,7 @@ public final class KeyboardLayoutSet {
final boolean asciiCapable = subtype.getRawSubtype().isAsciiCapable();
final boolean forceAscii = (mParams.mEditorInfo.imeOptions & EditorInfo.IME_FLAG_FORCE_ASCII) != 0;
mParams.mSubtype = (forceAscii && !asciiCapable)
? RichInputMethodSubtype.getNoLanguageSubtype()
? RichInputMethodSubtype.Companion.getNoLanguageSubtype()
: subtype;
return this;
}
@ -326,8 +326,8 @@ public final class KeyboardLayoutSet {
public static KeyboardId getFakeKeyboardId(final int elementId) {
final Params params = new Params();
params.mEditorInfo = new EditorInfo();
params.mSubtype = RichInputMethodSubtype.getEmojiSubtype();
params.mSubtype.getKeyboardLayoutSetName();
params.mSubtype = RichInputMethodSubtype.Companion.getEmojiSubtype();
params.mSubtype.getMainLayoutName();
return new KeyboardId(elementId, params);
}
}

View file

@ -161,7 +161,7 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions {
try {
final InputMethodSubtype qwerty = AdditionalSubtypeUtils.createEmojiCapableAdditionalSubtype(mRichImm.getCurrentSubtypeLocale(), "qwerty", true);
mKeyboardLayoutSet = builder.setKeyboardGeometry(keyboardWidth, keyboardHeight)
.setSubtype(new RichInputMethodSubtype(qwerty))
.setSubtype(RichInputMethodSubtype.Companion.get(qwerty))
.setVoiceInputKeyEnabled(settingsValues.mShowsVoiceInputKey)
.setNumberRowEnabled(settingsValues.mShowsNumberRow)
.setLanguageSwitchKeyEnabled(settingsValues.isLanguageSwitchKeyEnabled())

View file

@ -89,7 +89,7 @@ public final class EmojiPalettesView extends LinearLayout
final KeyboardLayoutSet.Builder builder = new KeyboardLayoutSet.Builder(context, null);
final Resources res = context.getResources();
mEmojiLayoutParams = new EmojiLayoutParams(res);
builder.setSubtype(RichInputMethodSubtype.getEmojiSubtype());
builder.setSubtype(RichInputMethodSubtype.Companion.getEmojiSubtype());
builder.setKeyboardGeometry(ResourceUtils.getKeyboardWidth(context, Settings.getInstance().getCurrent()),
mEmojiLayoutParams.getEmojiKeyboardHeight());
final KeyboardLayoutSet layoutSet = builder.build();

View file

@ -311,8 +311,8 @@ class KeyboardParser(private val params: KeyboardParams, private val context: Co
}
// some layouts have different number layout, and there we don't want the numbers on the top row
// todo: actually should not be in here, but in subtype extra values
private fun hasNumbersOnTopRow() = params.mId.mSubtype.keyboardLayoutSetName !in listOf("pcqwerty", "lao", "thai", "korean_sebeolsik_390", "korean_sebeolsik_final")
// todo: this should be derived from main layout and popup / hint order settings
private fun hasNumbersOnTopRow() = params.mId.mSubtype.mainLayoutName !in listOf("pcqwerty", "lao", "thai", "korean_sebeolsik_390", "korean_sebeolsik_final")
companion object {
private const val TAG = "KeyboardParser"

View file

@ -123,8 +123,8 @@ object RawKeyboardParser {
simpleKeyData.mapIndexedTo(mutableListOf()) { i, row ->
val newRow = row.toMutableList()
if (params.mId.isAlphabetKeyboard
&& params.mId.mSubtype.keyboardLayoutSetName.endsWith("+")
&& "$layoutName+" == params.mId.mSubtype.keyboardLayoutSetName
&& params.mId.mSubtype.mainLayoutName.endsWith("+")
&& "$layoutName+" == params.mId.mSubtype.mainLayoutName
) {
params.mLocaleKeyboardInfos.getExtraKeys(i+1)?.let { newRow.addAll(it) }
}
@ -145,7 +145,7 @@ object RawKeyboardParser {
KeyboardId.ELEMENT_PHONE_SYMBOLS -> LAYOUT_PHONE_SYMBOLS
KeyboardId.ELEMENT_EMOJI_BOTTOM_ROW -> LAYOUT_EMOJI_BOTTOM_ROW
KeyboardId.ELEMENT_CLIPBOARD_BOTTOM_ROW -> LAYOUT_CLIPBOARD_BOTTOM_ROW
else -> params.mId.mSubtype.keyboardLayoutSetName.substringBeforeLast("+")
else -> params.mId.mSubtype.mainLayoutName.substringBeforeLast("+")
}
private fun getFunctionalLayoutName(params: KeyboardParams, context: Context): String {

View file

@ -857,7 +857,7 @@ public class LatinIME extends InputMethodService implements
public void onCurrentInputMethodSubtypeChanged(final InputMethodSubtype subtype) {
// Note that the calling sequence of onCreate() and onCurrentInputMethodSubtypeChanged()
// is not guaranteed. It may even be called at the same time on a different thread.
if (subtype.hashCode() == 0xf000000f) {
if (subtype.hashCode() == 0x7000000f) {
// For some reason sometimes the system wants to set the dummy subtype, which messes with the currently enabled subtype.
// Now that the dummy subtype has a fixed id, we can easily avoid enabling it.
return;

View file

@ -206,14 +206,14 @@ public class RichInputMethodManager {
updateCurrentSubtype(newSubtype);
updateShortcutIme();
if (DEBUG) {
Log.w(TAG, "onSubtypeChanged: " + mCurrentRichInputMethodSubtype.getNameForLogging());
Log.w(TAG, "onSubtypeChanged: " + mCurrentRichInputMethodSubtype);
}
}
private static RichInputMethodSubtype sForcedSubtypeForTesting = null;
static void forceSubtype(@NonNull final InputMethodSubtype subtype) {
sForcedSubtypeForTesting = RichInputMethodSubtype.getRichInputMethodSubtype(subtype);
sForcedSubtypeForTesting = RichInputMethodSubtype.Companion.get(subtype);
}
@NonNull
@ -357,7 +357,7 @@ public class RichInputMethodManager {
private void updateCurrentSubtype(final InputMethodSubtype subtype) {
SubtypeSettingsKt.setSelectedSubtype(KtxKt.prefs(mContext), subtype);
mCurrentRichInputMethodSubtype = RichInputMethodSubtype.getRichInputMethodSubtype(subtype);
mCurrentRichInputMethodSubtype = RichInputMethodSubtype.Companion.get(subtype);
}
public static boolean canSwitchLanguage() {

View file

@ -1,220 +0,0 @@
/*
* Copyright (C) 2014 The Android Open Source Project
* modified
* SPDX-License-Identifier: Apache-2.0 AND GPL-3.0-only
*/
package helium314.keyboard.latin;
import android.view.inputmethod.InputMethodSubtype;
import helium314.keyboard.latin.common.Constants;
import helium314.keyboard.latin.common.LocaleUtils;
import helium314.keyboard.latin.utils.CustomLayoutUtilsKt;
import helium314.keyboard.latin.utils.Log;
import helium314.keyboard.latin.utils.SubtypeLocaleUtils;
import helium314.keyboard.latin.utils.SubtypeUtilsKt;
import java.util.Locale;
import static helium314.keyboard.latin.common.Constants.Subtype.KEYBOARD_MODE;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
/**
* Enrichment class for InputMethodSubtype to enable concurrent multi-lingual input.
* <p>
* Right now, this returns the extra value of its primary subtype.
*/
// non final for easy mocking.
public class RichInputMethodSubtype {
private static final String TAG = RichInputMethodSubtype.class.getSimpleName();
@NonNull
private final InputMethodSubtype mSubtype;
@NonNull
private final Locale mLocale;
// The subtype is considered RTL if the language of the main subtype is RTL.
// Cached because it might get read frequently, e.g. when moving pointer with space bar
private final boolean mIsRtl;
public RichInputMethodSubtype(@NonNull final InputMethodSubtype subtype) {
mSubtype = subtype;
mLocale = SubtypeUtilsKt.locale(mSubtype);
mIsRtl = LocaleUtils.isRtlLanguage(mLocale);
}
// Extra values are determined by the primary subtype. This is probably right, but
// we may have to revisit this later.
public String getExtraValueOf(@NonNull final String key) {
return mSubtype.getExtraValueOf(key);
}
public boolean hasExtraValue(@NonNull final String key) {
return mSubtype.containsExtraValueKey(key);
}
// The mode is also determined by the primary subtype.
public String getMode() {
return mSubtype.getMode();
}
public boolean isNoLanguage() {
return SubtypeLocaleUtils.NO_LANGUAGE.equals(mLocale.getLanguage());
}
public boolean isCustom() {
return getKeyboardLayoutSetName().startsWith(CustomLayoutUtilsKt.CUSTOM_LAYOUT_PREFIX);
}
public String getNameForLogging() {
return toString();
}
// InputMethodSubtype's display name for spacebar text in its locale.
// isAdditionalSubtype (T=true, F=false)
// locale layout | Middle Full
// ------ ------- - --------- ----------------------
// en_US qwerty F English English (US) exception
// en_GB qwerty F English English (UK) exception
// es_US spanish F Español Español (EE.UU.) exception
// fr azerty F Français Français
// fr_CA qwerty F Français Français (Canada)
// fr_CH swiss F Français Français (Suisse)
// de qwertz F Deutsch Deutsch
// de_CH swiss T Deutsch Deutsch (Schweiz)
// zz qwerty F QWERTY QWERTY
// fr qwertz T Français Français
// de qwerty T Deutsch Deutsch
// en_US azerty T English English (US)
// zz azerty T AZERTY AZERTY
// Get the RichInputMethodSubtype's full display name in its locale.
@NonNull
public String getFullDisplayName() {
if (isNoLanguage()) {
return SubtypeLocaleUtils.getKeyboardLayoutSetDisplayName(mSubtype);
}
return SubtypeLocaleUtils.getSubtypeLocaleDisplayName(mLocale);
}
// Get the RichInputMethodSubtype's middle display name in its locale.
@NonNull
public String getMiddleDisplayName() {
if (isNoLanguage()) {
return SubtypeLocaleUtils.getKeyboardLayoutSetDisplayName(mSubtype);
}
return SubtypeLocaleUtils.getSubtypeLanguageDisplayName(mLocale);
}
@Override
public boolean equals(final Object o) {
if (!(o instanceof RichInputMethodSubtype)) {
return false;
}
final RichInputMethodSubtype other = (RichInputMethodSubtype)o;
return mSubtype.equals(other.mSubtype) && mLocale.equals(other.mLocale);
}
@Override
public int hashCode() {
return mSubtype.hashCode() + mLocale.hashCode();
}
@Override
public String toString() {
return "Multi-lingual subtype: " + mSubtype + ", " + mLocale;
}
@NonNull
public Locale getLocale() {
return mLocale;
}
public boolean isRtlSubtype() {
return mIsRtl;
}
// TODO: remove this method
@NonNull
public InputMethodSubtype getRawSubtype() { return mSubtype; }
@NonNull
public String getKeyboardLayoutSetName() {
return SubtypeLocaleUtils.getKeyboardLayoutSetName(mSubtype);
}
public static RichInputMethodSubtype getRichInputMethodSubtype(
@Nullable final InputMethodSubtype subtype) {
if (subtype == null) {
return getNoLanguageSubtype();
} else {
return new RichInputMethodSubtype(subtype);
}
}
// Dummy no language QWERTY subtype. See {@link R.xml.method}.
private static final int SUBTYPE_ID_OF_DUMMY_NO_LANGUAGE_SUBTYPE = 0xdde0bfd3;
private static final String EXTRA_VALUE_OF_DUMMY_NO_LANGUAGE_SUBTYPE =
"KeyboardLayoutSet=" + SubtypeLocaleUtils.QWERTY
+ "," + Constants.Subtype.ExtraValue.ASCII_CAPABLE
+ "," + Constants.Subtype.ExtraValue.ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE
+ "," + Constants.Subtype.ExtraValue.EMOJI_CAPABLE;
@NonNull
private static final RichInputMethodSubtype DUMMY_NO_LANGUAGE_SUBTYPE =
new RichInputMethodSubtype(new InputMethodSubtype.InputMethodSubtypeBuilder()
.setSubtypeNameResId(R.string.subtype_no_language_qwerty)
.setSubtypeIconResId(R.drawable.ic_ime_switcher)
.setSubtypeLocale(SubtypeLocaleUtils.NO_LANGUAGE)
.setSubtypeMode(KEYBOARD_MODE)
.setSubtypeExtraValue(EXTRA_VALUE_OF_DUMMY_NO_LANGUAGE_SUBTYPE)
.setIsAuxiliary(false)
.setOverridesImplicitlyEnabledSubtype(false)
.setSubtypeId(SUBTYPE_ID_OF_DUMMY_NO_LANGUAGE_SUBTYPE)
.setIsAsciiCapable(true)
.build());
// Caveat: We probably should remove this when we add an Emoji subtype in {@link R.xml.method}.
// Dummy Emoji subtype. See {@link R.xml.method}.
private static final int SUBTYPE_ID_OF_DUMMY_EMOJI_SUBTYPE = 0xd78b2ed0;
private static final String EXTRA_VALUE_OF_DUMMY_EMOJI_SUBTYPE =
"KeyboardLayoutSet=" + SubtypeLocaleUtils.EMOJI
+ "," + Constants.Subtype.ExtraValue.EMOJI_CAPABLE;
@NonNull
private static final RichInputMethodSubtype DUMMY_EMOJI_SUBTYPE =
new RichInputMethodSubtype(new InputMethodSubtype.InputMethodSubtypeBuilder()
.setSubtypeNameResId(R.string.subtype_emoji)
.setSubtypeIconResId(R.drawable.ic_ime_switcher)
.setSubtypeLocale(SubtypeLocaleUtils.NO_LANGUAGE)
.setSubtypeMode(KEYBOARD_MODE)
.setSubtypeExtraValue(EXTRA_VALUE_OF_DUMMY_EMOJI_SUBTYPE)
.setIsAuxiliary(false)
.setOverridesImplicitlyEnabledSubtype(false)
.setSubtypeId(SUBTYPE_ID_OF_DUMMY_EMOJI_SUBTYPE)
.build());
private static RichInputMethodSubtype sNoLanguageSubtype;
@NonNull
public static RichInputMethodSubtype getNoLanguageSubtype() {
RichInputMethodSubtype noLanguageSubtype = sNoLanguageSubtype;
if (noLanguageSubtype == null) {
final InputMethodSubtype rawNoLanguageSubtype = RichInputMethodManager.getInstance()
.findSubtypeByLocaleAndKeyboardLayoutSet(LocaleUtils.constructLocale(SubtypeLocaleUtils.NO_LANGUAGE), SubtypeLocaleUtils.QWERTY);
if (rawNoLanguageSubtype != null) {
noLanguageSubtype = new RichInputMethodSubtype(rawNoLanguageSubtype);
}
}
if (noLanguageSubtype != null) {
sNoLanguageSubtype = noLanguageSubtype;
return noLanguageSubtype;
}
Log.w(TAG, "Can't find any language with QWERTY subtype");
Log.w(TAG, "No input method subtype found; returning dummy subtype: " + DUMMY_NO_LANGUAGE_SUBTYPE);
return DUMMY_NO_LANGUAGE_SUBTYPE;
}
@NonNull
public static RichInputMethodSubtype getEmojiSubtype() {
return DUMMY_EMOJI_SUBTYPE;
}
}

View file

@ -0,0 +1,134 @@
/*
* Copyright (C) 2014 The Android Open Source Project
* modified
* SPDX-License-Identifier: Apache-2.0 AND GPL-3.0-only
*/
package helium314.keyboard.latin
import android.view.inputmethod.InputMethodSubtype
import android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder
import helium314.keyboard.latin.common.Constants
import helium314.keyboard.latin.common.Constants.Subtype.ExtraValue.KEYBOARD_LAYOUT_SET
import helium314.keyboard.latin.common.LocaleUtils.constructLocale
import helium314.keyboard.latin.common.LocaleUtils.isRtlLanguage
import helium314.keyboard.latin.utils.CUSTOM_LAYOUT_PREFIX
import helium314.keyboard.latin.utils.LayoutType
import helium314.keyboard.latin.utils.Log
import helium314.keyboard.latin.utils.SubtypeLocaleUtils
import helium314.keyboard.latin.utils.locale
import java.util.Locale
/**
* Enrichment class for InputMethodSubtype that extracts settings from extra values
*/
class RichInputMethodSubtype private constructor(val rawSubtype: InputMethodSubtype) {
val locale: Locale = rawSubtype.locale()
// The subtype is considered RTL if the language of the main subtype is RTL.
val isRtlSubtype: Boolean = isRtlLanguage(locale)
fun getExtraValueOf(key: String): String? = rawSubtype.getExtraValueOf(key)
fun hasExtraValue(key: String): Boolean = rawSubtype.containsExtraValueKey(key)
val isNoLanguage: Boolean get() = SubtypeLocaleUtils.NO_LANGUAGE == locale.language
val mainLayoutName: String get() = layouts[LayoutType.MAIN] ?: "qwerty"
val layouts = LayoutType.getLayoutMap(getExtraValueOf(KEYBOARD_LAYOUT_SET) ?: "")
val isCustom: Boolean get() = mainLayoutName.startsWith(CUSTOM_LAYOUT_PREFIX)
val fullDisplayName: String get() {
if (isNoLanguage) {
return SubtypeLocaleUtils.getKeyboardLayoutSetDisplayName(rawSubtype)!!
}
return SubtypeLocaleUtils.getSubtypeLocaleDisplayName(locale)
}
val middleDisplayName: String
// Get the RichInputMethodSubtype's middle display name in its locale.
get() {
if (isNoLanguage) {
return SubtypeLocaleUtils.getKeyboardLayoutSetDisplayName(rawSubtype)!!
}
return SubtypeLocaleUtils.getSubtypeLanguageDisplayName(locale)
}
override fun equals(other: Any?): Boolean {
if (other !is RichInputMethodSubtype) return false
return rawSubtype == other.rawSubtype && locale == other.locale
}
override fun hashCode(): Int {
return rawSubtype.hashCode() + locale.hashCode()
}
override fun toString(): String = rawSubtype.extraValue
companion object {
private val TAG: String = RichInputMethodSubtype::class.java.simpleName
fun get(subtype: InputMethodSubtype?): RichInputMethodSubtype =
if (subtype == null) noLanguageSubtype
else RichInputMethodSubtype(subtype)
// Dummy no language QWERTY subtype. See method_dummy.xml}.
private const val EXTRA_VALUE_OF_DUMMY_NO_LANGUAGE_SUBTYPE = ("KeyboardLayoutSet=" + SubtypeLocaleUtils.QWERTY
+ "," + Constants.Subtype.ExtraValue.ASCII_CAPABLE
+ "," + Constants.Subtype.ExtraValue.ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE
+ "," + Constants.Subtype.ExtraValue.EMOJI_CAPABLE)
private val DUMMY_NO_LANGUAGE_SUBTYPE = RichInputMethodSubtype(
InputMethodSubtypeBuilder()
.setSubtypeNameResId(R.string.subtype_no_language_qwerty)
.setSubtypeIconResId(R.drawable.ic_ime_switcher)
.setSubtypeLocale(SubtypeLocaleUtils.NO_LANGUAGE)
.setSubtypeMode(Constants.Subtype.KEYBOARD_MODE)
.setSubtypeExtraValue(EXTRA_VALUE_OF_DUMMY_NO_LANGUAGE_SUBTYPE)
.setIsAuxiliary(false)
.setOverridesImplicitlyEnabledSubtype(false)
.setSubtypeId(0x7000000f)
.setIsAsciiCapable(true)
.build()
)
// Caveat: We probably should remove this when we add an Emoji subtype in {@link R.xml.method}.
// Dummy Emoji subtype. See {@link R.xml.method}.
private const val SUBTYPE_ID_OF_DUMMY_EMOJI_SUBTYPE = -0x2874d130
private const val EXTRA_VALUE_OF_DUMMY_EMOJI_SUBTYPE = ("KeyboardLayoutSet=" + SubtypeLocaleUtils.EMOJI
+ "," + Constants.Subtype.ExtraValue.EMOJI_CAPABLE)
val emojiSubtype: RichInputMethodSubtype = RichInputMethodSubtype(
InputMethodSubtypeBuilder()
.setSubtypeNameResId(R.string.subtype_emoji)
.setSubtypeIconResId(R.drawable.ic_ime_switcher)
.setSubtypeLocale(SubtypeLocaleUtils.NO_LANGUAGE)
.setSubtypeMode(Constants.Subtype.KEYBOARD_MODE)
.setSubtypeExtraValue(EXTRA_VALUE_OF_DUMMY_EMOJI_SUBTYPE)
.setIsAuxiliary(false)
.setOverridesImplicitlyEnabledSubtype(false)
.setSubtypeId(SUBTYPE_ID_OF_DUMMY_EMOJI_SUBTYPE)
.build()
)
private var sNoLanguageSubtype: RichInputMethodSubtype? = null
val noLanguageSubtype: RichInputMethodSubtype get() {
sNoLanguageSubtype?.let { return it }
var noLanguageSubtype = sNoLanguageSubtype
val rawNoLanguageSubtype = RichInputMethodManager.getInstance()
.findSubtypeByLocaleAndKeyboardLayoutSet(
SubtypeLocaleUtils.NO_LANGUAGE.constructLocale(),
SubtypeLocaleUtils.QWERTY
)
if (rawNoLanguageSubtype != null) {
noLanguageSubtype = RichInputMethodSubtype(rawNoLanguageSubtype)
}
if (noLanguageSubtype != null) {
sNoLanguageSubtype = noLanguageSubtype
return noLanguageSubtype
}
Log.w(TAG, "Can't find any language with QWERTY subtype")
Log.w(TAG, "No input method subtype found; returning dummy subtype: $DUMMY_NO_LANGUAGE_SUBTYPE")
return DUMMY_NO_LANGUAGE_SUBTYPE
}
}
}

View file

@ -41,35 +41,20 @@ public final class Constants {
}
public static final class Subtype {
/**
* The subtype mode used to indicate that the subtype is a keyboard.
*/
/** The subtype mode used to indicate that the subtype is a keyboard. */
public static final String KEYBOARD_MODE = "keyboard";
// some extra values:
// TrySuppressingImeSwitcher: not documented, but used in Android source
// AsciiCapable: not used, but recommended for Android 9- because of known issues
// SupportTouchPositionCorrection: never read, never used outside AOSP keyboard -> can be removed?
// EmojiCapable: there is some description in Constants, but actually it's never read
// KeyboardLayoutSet: obvious
public static final class ExtraValue {
/**
* The subtype extra value used to indicate that this subtype is capable of
* entering ASCII characters.
*/
/** Indicates that this subtype is capable of entering ASCII characters (not used, but recommended for Android 9 and older). */
public static final String ASCII_CAPABLE = "AsciiCapable";
/**
* The subtype extra value used to indicate that this subtype is enabled
* when the default subtype is not marked as ascii capable.
*/
public static final String ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE =
"EnabledWhenDefaultIsNotAsciiCapable";
/** Indicates that this subtype is enabled when the default subtype is not marked as ascii capable (used where?). */
public static final String ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE = "EnabledWhenDefaultIsNotAsciiCapable";
/**
* The subtype extra value used to indicate that this subtype is capable of
* entering emoji characters.
*/
/** Indicates that this subtype is capable of entering emoji characters (always set?). */
public static final String EMOJI_CAPABLE = "EmojiCapable";
/** Indicates that the subtype does not have a shift key */
@ -84,26 +69,29 @@ public final class Constants {
* this extra value.
* This extra value is supported on JellyBean and later.
*/
public static final String UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME =
"UntranslatableReplacementStringInSubtypeName";
public static final String UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME = "UntranslatableReplacementStringInSubtypeName";
/**
* The subtype extra value used to indicate this subtype keyboard layout set name.
* This extra value is private to LatinIME.
*/
/** Contains the layouts used by this subtype. This extra value is private to LatinIME.*/
public static final String KEYBOARD_LAYOUT_SET = "KeyboardLayoutSet";
/**
* The subtype extra value used to indicate that this subtype is an additional subtype
* that the user defined. This extra value is private to LatinIME.
*/
/** Indicates that this subtype is an additional subtype that the user defined. This extra value is private to LatinIME. */
public static final String IS_ADDITIONAL_SUBTYPE = "isAdditionalSubtype";
/**
* The subtype extra value used to specify the combining rules.
*/
/** The subtype extra value used to specify the combining rules (currently not used). */
public static final String COMBINING_RULES = "CombiningRules";
/** Overrides the general popup order setting */
public static final String POPUP_ORDER = "PopupOrder";
/** Overrides the general hint order / priority setting */
public static final String HINT_ORDER = "HintOrder";
/** Language tags indicating enabled secondary locales */
public static final String SECONDARY_LOCALES = "SecondaryLocales";
/** Overrides the general "more popups" setting */
public static final String MORE_POPUPS = "MorePopups";
private ExtraValue() {
// This utility class is not publicly instantiable.
}

View file

@ -22,7 +22,6 @@ import helium314.keyboard.dictionarypack.DictionaryPackConstants
import helium314.keyboard.keyboard.KeyboardLayoutSet
import helium314.keyboard.keyboard.KeyboardSwitcher
import helium314.keyboard.latin.R
import helium314.keyboard.latin.common.Constants.Subtype.ExtraValue.KEYBOARD_LAYOUT_SET
import helium314.keyboard.latin.common.LocaleUtils
import helium314.keyboard.latin.common.LocaleUtils.constructLocale
import helium314.keyboard.latin.databinding.LanguageListItemBinding
@ -138,10 +137,10 @@ class LanguageSettingsDialog(
val layouts = mutableListOf<String>()
val displayNames = mutableListOf<String>()
infos.forEach {
val layoutSetName = it.subtype.getExtraValueOf(KEYBOARD_LAYOUT_SET)
if (layoutSetName?.startsWith(CUSTOM_LAYOUT_PREFIX) == false // don't allow copying custom layout (at least for now)
&& !layoutSetName.endsWith("+")) { // don't allow copying layouts only defined via extra keys
layouts.add(layoutSetName)
val mainLayoutName = it.subtype.mainLayoutName()
if (!mainLayoutName.startsWith(CUSTOM_LAYOUT_PREFIX) // don't allow copying custom layout (at least for now)
&& !mainLayoutName.endsWith("+")) { // don't allow copying layouts only defined via extra keys
layouts.add(mainLayoutName)
displayNames.add(it.subtype.displayName(context).toString())
}
}
@ -169,7 +168,7 @@ class LanguageSettingsDialog(
private fun addSubtypeToView(subtype: SubtypeInfo) {
val row = LayoutInflater.from(context).inflate(R.layout.language_list_item, listView)
val layoutSetName = subtype.subtype.getExtraValueOf(KEYBOARD_LAYOUT_SET) ?: "qwerty"
val layoutSetName = subtype.subtype.mainLayoutName()
row.findViewById<TextView>(R.id.language_name).text =
SubtypeLocaleUtils.getKeyboardLayoutSetDisplayName(subtype.subtype)
?: subtype.subtype.displayName(context)

View file

@ -211,7 +211,7 @@ public final class AndroidSpellCheckerService extends SpellCheckerService
final KeyboardLayoutSet.Builder builder = new KeyboardLayoutSet.Builder(this, editorInfo);
return builder
.setKeyboardGeometry(SPELLCHECKER_DUMMY_KEYBOARD_WIDTH, SPELLCHECKER_DUMMY_KEYBOARD_HEIGHT)
.setSubtype(RichInputMethodSubtype.getRichInputMethodSubtype(subtype))
.setSubtype(RichInputMethodSubtype.Companion.get(subtype))
.setIsSpellChecker(true)
.disableTouchPositionCorrectionData()
.build();

View file

@ -81,7 +81,7 @@ public final class AdditionalSubtypeUtils {
private static String getPrefSubtype(final InputMethodSubtype subtype) {
final String keyboardLayoutSetName = SubtypeLocaleUtils.getKeyboardLayoutSetName(subtype);
final String layoutExtraValue = KEYBOARD_LAYOUT_SET + "=" + keyboardLayoutSetName;
final String layoutExtraValue = KEYBOARD_LAYOUT_SET + "=MAIN:" + keyboardLayoutSetName;
final String extraValue = StringUtils.removeFromCommaSplittableTextIfExists(
layoutExtraValue, StringUtils.removeFromCommaSplittableTextIfExists(
IS_ADDITIONAL_SUBTYPE, subtype.getExtraValue()));
@ -173,7 +173,7 @@ public final class AdditionalSubtypeUtils {
final String keyboardLayoutSetName, final boolean isAsciiCapable,
final boolean isEmojiCapable) {
final ArrayList<String> extraValueItems = new ArrayList<>();
extraValueItems.add(KEYBOARD_LAYOUT_SET + "=" + keyboardLayoutSetName);
extraValueItems.add(KEYBOARD_LAYOUT_SET + "=MAIN:" + keyboardLayoutSetName);
if (isAsciiCapable) {
extraValueItems.add(ASCII_CAPABLE);
}
@ -213,7 +213,7 @@ public final class AdditionalSubtypeUtils {
// - EmojiCapable
// - isAdditionalSubtype
final ArrayList<String> compatibilityExtraValueItems = new ArrayList<>();
compatibilityExtraValueItems.add(KEYBOARD_LAYOUT_SET + "=" + keyboardLayoutSetName);
compatibilityExtraValueItems.add(KEYBOARD_LAYOUT_SET + "=MAIN:" + keyboardLayoutSetName);
compatibilityExtraValueItems.add(ASCII_CAPABLE);
if (SubtypeLocaleUtils.isExceptionalLocale(locale)) {
compatibilityExtraValueItems.add(UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME + "=" +

View file

@ -224,7 +224,7 @@ fun getCustomFunctionalLayoutName(elementId: Int, subtype: InputMethodSubtype, c
val customFunctionalLayoutNames = getCustomLayoutFiles(context).filter { it.name.contains("functional") }.map { it.name.substringBeforeLast(".") + "." }
if (customFunctionalLayoutNames.isEmpty()) return null
val languageTag = subtype.locale().toLanguageTag()
val mainLayoutName = subtype.getExtraValueOf(Constants.Subtype.ExtraValue.KEYBOARD_LAYOUT_SET) ?: "qwerty"
val mainLayoutName = subtype.mainLayoutName()
if (elementId == KeyboardId.ELEMENT_SYMBOLS_SHIFTED) {
findMatchingLayout(customFunctionalLayoutNames.filter { it.startsWith(CUSTOM_FUNCTIONAL_LAYOUT_SYMBOLS_SHIFTED) }, mainLayoutName, languageTag)

View file

@ -47,7 +47,7 @@ public final class LanguageOnSpacebarUtils {
return FORMAT_TYPE_NONE;
}
final String keyboardLanguage = locale.getLanguage();
final String keyboardLayout = subtype.getKeyboardLayoutSetName();
final String keyboardLayout = subtype.getMainLayoutName();
int sameLanguageAndLayoutCount = 0;
for (final InputMethodSubtype ims : sEnabledSubtypes) {
final String language = SubtypeUtilsKt.locale(ims).getLanguage();

View file

@ -0,0 +1,21 @@
package helium314.keyboard.latin.utils
import java.util.EnumMap
enum class LayoutType {
MAIN, SYMBOLS, MORE_SYMBOLS, FUNCTIONAL, NUMBER, NUMBER_ROW, NUMPAD,
NUMPAD_LANDSCAPE, PHONE, PHONE_SYMBOLS, EMOJI_BOTTOM, CLIPBOARD_BOTTOM;
companion object {
fun EnumMap<LayoutType, String>.toExtraValue() = map { "${it.key.name}:${it.value}" }.joinToString("|")
fun getLayoutMap(extraValue: String): EnumMap<LayoutType, String> {
val map = EnumMap<LayoutType, String>(LayoutType::class.java)
extraValue.split("|").forEach {
val s = it.split(":")
runCatching { map[LayoutType.valueOf(s[0])] = s[1] }
}
return map
}
}
}

View file

@ -19,7 +19,6 @@ 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.KEYBOARD_LAYOUT_SET;
import static helium314.keyboard.latin.common.Constants.Subtype.ExtraValue.UNTRANSLATABLE_STRING_IN_SUBTYPE_NAME;
import androidx.annotation.NonNull;
@ -274,7 +273,7 @@ public final class SubtypeLocaleUtils {
@NonNull
public static String getKeyboardLayoutSetName(final InputMethodSubtype subtype) {
String keyboardLayoutSet = subtype.getExtraValueOf(KEYBOARD_LAYOUT_SET);
String keyboardLayoutSet = SubtypeUtilsKt.explicitMainLayoutName(subtype);
if (keyboardLayoutSet == null && subtype.isAsciiCapable()) {
keyboardLayoutSet = QWERTY;
}

View file

@ -41,7 +41,7 @@ fun getAllAvailableSubtypes(): List<InputMethodSubtype> {
fun getMatchingLayoutSetNameForLocale(locale: Locale): String {
val subtypes = resourceSubtypesByLocale.values.flatten()
val name = LocaleUtils.getBestMatch(locale, subtypes) { it.locale() }?.getExtraValueOf(Constants.Subtype.ExtraValue.KEYBOARD_LAYOUT_SET)
val name = LocaleUtils.getBestMatch(locale, subtypes) { it.locale() }?.explicitMainLayoutName()
if (name != null) return name
return when (locale.script()) {
ScriptUtils.SCRIPT_LATIN -> "qwerty"

View file

@ -3,6 +3,7 @@ package helium314.keyboard.latin.utils
import android.content.Context
import android.os.Build
import android.view.inputmethod.InputMethodSubtype
import helium314.keyboard.latin.common.Constants.Subtype.ExtraValue.KEYBOARD_LAYOUT_SET
import helium314.keyboard.latin.common.LocaleUtils
import helium314.keyboard.latin.common.LocaleUtils.constructLocale
import java.util.Locale
@ -15,6 +16,16 @@ fun InputMethodSubtype.locale(): Locale {
@Suppress("deprecation") return locale.constructLocale()
}
fun InputMethodSubtype.mainLayoutName(): String {
val map = LayoutType.getLayoutMap(getExtraValueOf(KEYBOARD_LAYOUT_SET) ?: "")
return map[LayoutType.MAIN] ?: "qwerty"
}
fun InputMethodSubtype.explicitMainLayoutName(): String? {
val map = LayoutType.getLayoutMap(getExtraValueOf(KEYBOARD_LAYOUT_SET) ?: "")
return map[LayoutType.MAIN]
}
/** Workaround for SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale ignoring custom layout names */
// todo (later): this should be done properly and in SubtypeLocaleUtils
fun InputMethodSubtype.displayName(context: Context): CharSequence {

View file

@ -7,7 +7,6 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.unit.LayoutDirection
import androidx.navigation.NavOptions
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
@ -57,6 +56,7 @@ fun SettingsNavHost(
onClickGestureTyping = { navController.navigate(SettingsDestination.GestureTyping) },
onClickAdvanced = { navController.navigate(SettingsDestination.Advanced) },
onClickAppearance = { navController.navigate(SettingsDestination.Appearance) },
onClickLanguage = { navController.navigate(SettingsDestination.Languages) },
onClickBack = ::goBack,
)
}
@ -90,9 +90,7 @@ fun SettingsNavHost(
// )
}
composable(SettingsDestination.Languages) {
// LanguagesSettingsScreen(
// onClickBack = ::goBack
// )
// LanguageScreen(onClickBack = ::goBack)
}
composable(SettingsDestination.Colors) {
ColorsScreen(isNight = false, onClickBack = ::goBack)

View file

@ -39,6 +39,7 @@ fun MainSettingsScreen(
onClickGestureTyping: () -> Unit,
onClickAdvanced: () -> Unit,
onClickAppearance: () -> Unit,
onClickLanguage: () -> Unit,
onClickBack: () -> Unit,
) {
val ctx = LocalContext.current
@ -48,6 +49,17 @@ fun MainSettingsScreen(
settings = emptyList(),
) {
Column(Modifier.verticalScroll(rememberScrollState())) {
Preference(
name = stringResource(R.string.language_and_layouts_title),
onClick = onClickLanguage,
icon = R.drawable.ic_settings_languages_foreground
) {
Icon(
painter = painterResource(R.drawable.ic_arrow_left),
modifier = Modifier.scale(-1f, 1f),
contentDescription = null
)
}
Preference(
name = stringResource(R.string.settings_screen_preferences),
onClick = onClickPreferences,
@ -169,7 +181,7 @@ fun MainSettingsScreen(
private fun PreviewScreen() {
Theme(true) {
Surface {
MainSettingsScreen({}, {}, {}, {}, {}, {}, {}, {})
MainSettingsScreen({}, {}, {}, {}, {}, {}, {}, {}, {})
}
}
}