From 49c9d77978e2fc6b281a7d1bb3c91bde991aa0b7 Mon Sep 17 00:00:00 2001 From: Helium314 Date: Wed, 11 Jun 2025 22:15:49 +0200 Subject: [PATCH 01/26] try more aggressive proguard settings --- app/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 93bb05765..97bad787c 100755 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -18,7 +18,7 @@ android { abiFilters.clear() abiFilters.addAll(listOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64")) } - proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro") + proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") } buildTypes { From 871ac110ad5de4b0fffc8838733c07805fb7bb92 Mon Sep 17 00:00:00 2001 From: Eran Leshem <1707552+eranl@users.noreply.github.com> Date: Wed, 11 Jun 2025 23:16:39 +0300 Subject: [PATCH 02/26] Move non-`com` global TLDs to end of list (#1668) --- .../keyboard_parser/LocaleKeyboardInfos.kt | 42 +++++++------------ 1 file changed, 16 insertions(+), 26 deletions(-) diff --git a/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/LocaleKeyboardInfos.kt b/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/LocaleKeyboardInfos.kt index 1184b8ea4..a67fca6bd 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/LocaleKeyboardInfos.kt +++ b/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/LocaleKeyboardInfos.kt @@ -45,7 +45,7 @@ class LocaleKeyboardInfos(dataStream: InputStream?, locale: Locale) { "mns" -> Key.LABEL_FLAGS_FOLLOW_KEY_LETTER_RATIO else -> 0 } - val tlds = getLocaleTlds(locale) + val tlds = mutableListOf() init { readStream(dataStream, false, true) @@ -84,7 +84,7 @@ class LocaleKeyboardInfos(dataStream: InputStream?, locale: Locale) { READER_MODE_EXTRA_KEYS -> if (!onlyPopupKeys) addExtraKey(line.split(colonSpaceRegex, 2)) READER_MODE_LABELS -> if (!onlyPopupKeys) addLabel(line.split(colonSpaceRegex, 2)) READER_MODE_NUMBER_ROW -> localizedNumberKeys = line.splitOnWhitespace() - READER_MODE_TLD -> SpacedTokens(line).forEach { tlds.add(".$it") } + READER_MODE_TLD -> tlds.addAll(SpacedTokens(line).map { ".$it" }) } } } @@ -156,6 +156,16 @@ class LocaleKeyboardInfos(dataStream: InputStream?, locale: Locale) { } } + fun addLocaleTlds(locale: Locale) { + tlds.add(0, comTld) + val ccLower = locale.country.lowercase() + if (ccLower.isNotEmpty() && locale.language != SubtypeLocaleUtils.NO_LANGUAGE) { + specialCountryTlds[ccLower]?.let { tlds.addAll(SpacedTokens(it)) } ?: tlds.add(".$ccLower") + } + if ((locale.language != "en" && euroLocales.matches(locale.language)) || euroCountries.matches(locale.country)) + tlds.add(".eu") + tlds.addAll(SpacedTokens(otherDefaultTlds)) + } } private fun addFixedColumnOrder(popupKeys: MutableCollection) { @@ -205,6 +215,7 @@ private fun createLocaleKeyTexts(context: Context, params: KeyboardParams, popup POPUP_KEYS_MORE -> lkt.addFile(context.assets.open("$LOCALE_TEXTS_FOLDER/more_popups_more.txt"), false) POPUP_KEYS_ALL -> lkt.addFile(context.assets.open("$LOCALE_TEXTS_FOLDER/more_popups_all.txt"), false) } + lkt.addLocaleTlds(params.mId.locale) return lkt } @@ -220,28 +231,6 @@ private fun getStreamForLocale(locale: Locale, context: Context) = } } -private fun getLocaleTlds(locale: Locale): LinkedHashSet { - val tlds = getDefaultTlds(locale) - val ccLower = locale.country.lowercase() - if (ccLower.isEmpty() || locale.language == SubtypeLocaleUtils.NO_LANGUAGE) - return tlds - specialCountryTlds.forEach { - if (ccLower != it.first) return@forEach - tlds.addAll(SpacedTokens(it.second)) - return@getLocaleTlds tlds - } - tlds.add(".$ccLower") - return tlds -} - -private fun getDefaultTlds(locale: Locale): LinkedHashSet { - val tlds = linkedSetOf() - tlds.addAll(SpacedTokens(defaultTlds)) - if ((locale.language != "en" && euroLocales.matches(locale.language)) || euroCountries.matches(locale.country)) - tlds.add(".eu") - return tlds -} - fun clearCache() = localeKeyboardInfosCache.clear() // cache the texts, so they don't need to be read over and over @@ -341,7 +330,7 @@ const val POPUP_KEYS_NORMAL = "normal" private const val LOCALE_TEXTS_FOLDER = "locale_key_texts" // either tld is not simply lowercase ISO 3166-1 code, or there are multiple according to some list -private val specialCountryTlds = listOf( +private val specialCountryTlds = hashMapOf( "bd" to ".bd .com.bd", "bq" to ".bq .an .nl", "bl" to ".bl .gp .fr", @@ -351,4 +340,5 @@ private val specialCountryTlds = listOf( "mf" to ".mf .gp .fr", "tl" to ".tl .tp", ) -private const val defaultTlds = ".com .gov .edu .org .net" +private const val comTld = ".com" +private const val otherDefaultTlds = ".gov .edu .org .net" From d5cd18ecaae1fc344d16320cafbee1699cff2f6d Mon Sep 17 00:00:00 2001 From: Eran Leshem <1707552+eranl@users.noreply.github.com> Date: Wed, 11 Jun 2025 23:17:05 +0300 Subject: [PATCH 03/26] Remove regional indicator symbol letters from emoji list (#1680) --- app/src/main/assets/emoji/SYMBOLS.txt | 28 +-------- .../tools/emoji/model/EmojiData.kt | 62 ------------------- .../resources/emoji/android-emoji-support.txt | 26 -------- 3 files changed, 1 insertion(+), 115 deletions(-) diff --git a/app/src/main/assets/emoji/SYMBOLS.txt b/app/src/main/assets/emoji/SYMBOLS.txt index f85cc13bb..02effebc7 100644 --- a/app/src/main/assets/emoji/SYMBOLS.txt +++ b/app/src/main/assets/emoji/SYMBOLS.txt @@ -136,32 +136,6 @@ ®️ ™️ 🫟 -🇦 -🇧 -🇨 -🇩 -🇪 -🇫 -🇬 -🇭 -🇮 -🇯 -🇰 -🇱 -🇲 -🇳 -🇴 -🇵 -🇶 -🇷 -🇸 -🇹 -🇺 -🇻 -🇼 -🇽 -🇾 -🇿 #️⃣ *️⃣ 0️⃣ @@ -247,4 +221,4 @@ 💠 🔘 🔳 -🔲 \ No newline at end of file +🔲 diff --git a/tools/make-emoji-keys/src/main/kotlin/com/majeur/inputmethod/tools/emoji/model/EmojiData.kt b/tools/make-emoji-keys/src/main/kotlin/com/majeur/inputmethod/tools/emoji/model/EmojiData.kt index e8fe8f868..d4f73733a 100644 --- a/tools/make-emoji-keys/src/main/kotlin/com/majeur/inputmethod/tools/emoji/model/EmojiData.kt +++ b/tools/make-emoji-keys/src/main/kotlin/com/majeur/inputmethod/tools/emoji/model/EmojiData.kt @@ -33,36 +33,6 @@ class EmojiData { } private fun onEmojiInserted(group: EmojiGroup, emoji: EmojiSpec): Boolean { - // Unicode RGI does not include letter symbols but Android supports them, so we inject them manually. - if (emoji.codes contentEquals RAW_CPS_KEYCAP_HASH) { - insertEmoji(group, intArrayOf(CP_REGIONAL_INDICATOR_SYMBOL_LETTER_A), 2.0f, "regional indicator symbol letter a") - insertEmoji(group, intArrayOf(CP_REGIONAL_INDICATOR_SYMBOL_LETTER_B), 2.0f, "regional indicator symbol letter b") - insertEmoji(group, intArrayOf(CP_REGIONAL_INDICATOR_SYMBOL_LETTER_C), 2.0f, "regional indicator symbol letter c") - insertEmoji(group, intArrayOf(CP_REGIONAL_INDICATOR_SYMBOL_LETTER_D), 2.0f, "regional indicator symbol letter d") - insertEmoji(group, intArrayOf(CP_REGIONAL_INDICATOR_SYMBOL_LETTER_E), 2.0f, "regional indicator symbol letter e") - insertEmoji(group, intArrayOf(CP_REGIONAL_INDICATOR_SYMBOL_LETTER_F), 2.0f, "regional indicator symbol letter f") - insertEmoji(group, intArrayOf(CP_REGIONAL_INDICATOR_SYMBOL_LETTER_G), 2.0f, "regional indicator symbol letter g") - insertEmoji(group, intArrayOf(CP_REGIONAL_INDICATOR_SYMBOL_LETTER_H), 2.0f, "regional indicator symbol letter h") - insertEmoji(group, intArrayOf(CP_REGIONAL_INDICATOR_SYMBOL_LETTER_I), 2.0f, "regional indicator symbol letter i") - insertEmoji(group, intArrayOf(CP_REGIONAL_INDICATOR_SYMBOL_LETTER_J), 2.0f, "regional indicator symbol letter j") - insertEmoji(group, intArrayOf(CP_REGIONAL_INDICATOR_SYMBOL_LETTER_K), 2.0f, "regional indicator symbol letter k") - insertEmoji(group, intArrayOf(CP_REGIONAL_INDICATOR_SYMBOL_LETTER_L), 2.0f, "regional indicator symbol letter l") - insertEmoji(group, intArrayOf(CP_REGIONAL_INDICATOR_SYMBOL_LETTER_M), 2.0f, "regional indicator symbol letter m") - insertEmoji(group, intArrayOf(CP_REGIONAL_INDICATOR_SYMBOL_LETTER_N), 2.0f, "regional indicator symbol letter n") - insertEmoji(group, intArrayOf(CP_REGIONAL_INDICATOR_SYMBOL_LETTER_O), 2.0f, "regional indicator symbol letter o") - insertEmoji(group, intArrayOf(CP_REGIONAL_INDICATOR_SYMBOL_LETTER_P), 2.0f, "regional indicator symbol letter p") - insertEmoji(group, intArrayOf(CP_REGIONAL_INDICATOR_SYMBOL_LETTER_Q), 2.0f, "regional indicator symbol letter q") - insertEmoji(group, intArrayOf(CP_REGIONAL_INDICATOR_SYMBOL_LETTER_R), 2.0f, "regional indicator symbol letter r") - insertEmoji(group, intArrayOf(CP_REGIONAL_INDICATOR_SYMBOL_LETTER_S), 2.0f, "regional indicator symbol letter s") - insertEmoji(group, intArrayOf(CP_REGIONAL_INDICATOR_SYMBOL_LETTER_T), 2.0f, "regional indicator symbol letter t") - insertEmoji(group, intArrayOf(CP_REGIONAL_INDICATOR_SYMBOL_LETTER_U), 2.0f, "regional indicator symbol letter u") - insertEmoji(group, intArrayOf(CP_REGIONAL_INDICATOR_SYMBOL_LETTER_V), 2.0f, "regional indicator symbol letter v") - insertEmoji(group, intArrayOf(CP_REGIONAL_INDICATOR_SYMBOL_LETTER_W), 2.0f, "regional indicator symbol letter w") - insertEmoji(group, intArrayOf(CP_REGIONAL_INDICATOR_SYMBOL_LETTER_X), 2.0f, "regional indicator symbol letter x") - insertEmoji(group, intArrayOf(CP_REGIONAL_INDICATOR_SYMBOL_LETTER_Y), 2.0f, "regional indicator symbol letter y") - insertEmoji(group, intArrayOf(CP_REGIONAL_INDICATOR_SYMBOL_LETTER_Z), 2.0f, "regional indicator symbol letter z") - } - // Some multi-skin-tone variants use a different base code than their non-multi-skin-tone counterparts, // so they don't get grouped. We drop them here, to prevent each variant from being displayed separately. return ! hasMultipleSkinModifiers(emoji.codes) @@ -118,9 +88,6 @@ class EmojiData { } companion object { - - private val RAW_CPS_KEYCAP_HASH = intArrayOf(0x0023, 0xFE0F, 0x20E3) - const val CP_NUL = 0x0000 private const val CP_ZWJ = 0x200D @@ -136,34 +103,5 @@ class EmojiData { private const val CP_WHITE_HAIR = 0x1F9B3 private const val CP_BARLD = 0x1F9B2 private const val CP_VARIANT_SELECTOR = 0xFE0F - - private const val CP_REGIONAL_INDICATOR_SYMBOL_LETTER_A = 0x1F1E6 - private const val CP_REGIONAL_INDICATOR_SYMBOL_LETTER_B = 0x1F1E7 - private const val CP_REGIONAL_INDICATOR_SYMBOL_LETTER_C = 0x1F1E8 - private const val CP_REGIONAL_INDICATOR_SYMBOL_LETTER_D = 0x1F1E9 - private const val CP_REGIONAL_INDICATOR_SYMBOL_LETTER_E = 0x1F1EA - private const val CP_REGIONAL_INDICATOR_SYMBOL_LETTER_F = 0x1F1EB - private const val CP_REGIONAL_INDICATOR_SYMBOL_LETTER_G = 0x1F1EC - private const val CP_REGIONAL_INDICATOR_SYMBOL_LETTER_H = 0x1F1ED - private const val CP_REGIONAL_INDICATOR_SYMBOL_LETTER_I = 0x1F1EE - private const val CP_REGIONAL_INDICATOR_SYMBOL_LETTER_J = 0x1F1EF - private const val CP_REGIONAL_INDICATOR_SYMBOL_LETTER_K = 0x1F1F0 - private const val CP_REGIONAL_INDICATOR_SYMBOL_LETTER_L = 0x1F1F1 - private const val CP_REGIONAL_INDICATOR_SYMBOL_LETTER_M = 0x1F1F2 - private const val CP_REGIONAL_INDICATOR_SYMBOL_LETTER_N = 0x1F1F3 - private const val CP_REGIONAL_INDICATOR_SYMBOL_LETTER_O = 0x1F1F4 - private const val CP_REGIONAL_INDICATOR_SYMBOL_LETTER_P = 0x1F1F5 - private const val CP_REGIONAL_INDICATOR_SYMBOL_LETTER_Q = 0x1F1F6 - private const val CP_REGIONAL_INDICATOR_SYMBOL_LETTER_R = 0x1F1F7 - private const val CP_REGIONAL_INDICATOR_SYMBOL_LETTER_S = 0x1F1F8 - private const val CP_REGIONAL_INDICATOR_SYMBOL_LETTER_T = 0x1F1F9 - private const val CP_REGIONAL_INDICATOR_SYMBOL_LETTER_U = 0x1F1FA - private const val CP_REGIONAL_INDICATOR_SYMBOL_LETTER_V = 0x1F1FB - private const val CP_REGIONAL_INDICATOR_SYMBOL_LETTER_W = 0x1F1FC - private const val CP_REGIONAL_INDICATOR_SYMBOL_LETTER_X = 0x1F1FD - private const val CP_REGIONAL_INDICATOR_SYMBOL_LETTER_Y = 0x1F1FE - private const val CP_REGIONAL_INDICATOR_SYMBOL_LETTER_Z = 0x1F1FF } - - } diff --git a/tools/make-emoji-keys/src/main/resources/emoji/android-emoji-support.txt b/tools/make-emoji-keys/src/main/resources/emoji/android-emoji-support.txt index 15aff89ab..baaf17988 100644 --- a/tools/make-emoji-keys/src/main/resources/emoji/android-emoji-support.txt +++ b/tools/make-emoji-keys/src/main/resources/emoji/android-emoji-support.txt @@ -3866,31 +3866,5 @@ U+1F532 # black square button U+1F3C1 # chequered flag U+1F6A9 # triangular flag U+1F38C # crossed flags -U+1F1E6 # regional indicator symbol letter a -U+1F1E7 # regional indicator symbol letter b -U+1F1E8 # regional indicator symbol letter c -U+1F1E9 # regional indicator symbol letter d -U+1F1EA # regional indicator symbol letter e -U+1F1EB # regional indicator symbol letter f -U+1F1EC # regional indicator symbol letter g -U+1F1ED # regional indicator symbol letter h -U+1F1EE # regional indicator symbol letter i -U+1F1EF # regional indicator symbol letter j -U+1F1F0 # regional indicator symbol letter k -U+1F1F1 # regional indicator symbol letter l -U+1F1F2 # regional indicator symbol letter m -U+1F1F3 # regional indicator symbol letter n -U+1F1F4 # regional indicator symbol letter o -U+1F1F5 # regional indicator symbol letter p -U+1F1F6 # regional indicator symbol letter q -U+1F1F7 # regional indicator symbol letter r -U+1F1F8 # regional indicator symbol letter s -U+1F1F9 # regional indicator symbol letter t -U+1F1FA # regional indicator symbol letter u -U+1F1FB # regional indicator symbol letter v -U+1F1FC # regional indicator symbol letter w -U+1F1FD # regional indicator symbol letter x -U+1F1FE # regional indicator symbol letter y -U+1F1FF # regional indicator symbol letter z # Above emojis are supported from Android 4.4 (API level 19) From e9e3bdac17385bb08c1344b0336b1c44dbb2babf Mon Sep 17 00:00:00 2001 From: Eran Leshem <1707552+eranl@users.noreply.github.com> Date: Wed, 11 Jun 2025 23:17:26 +0300 Subject: [PATCH 04/26] Display emoji descriptions in popups (#1542) --- .../keyboard/keyboard/KeyboardSwitcher.java | 6 +- .../keyboard/PopupKeysKeyboardView.java | 43 ++----- .../keyboard/keyboard/PopupKeysPanel.java | 37 ++++-- .../keyboard/keyboard/PopupTextView.java | 121 ++++++++++++++++++ .../keyboard/emoji/EmojiPageKeyboardView.java | 87 +++++++++---- .../keyboard/emoji/EmojiPalettesAdapter.java | 9 +- .../keyboard/emoji/EmojiPalettesView.java | 58 +++++++-- ...ntListener.java => EmojiViewCallback.java} | 13 +- .../helium314/keyboard/latin/Dictionary.java | 5 + .../keyboard/latin/DictionaryFactory.kt | 33 +++-- .../keyboard/latin/KoreanDictionary.java | 6 + .../helium314/keyboard/latin/LatinIME.java | 9 +- .../latin/ReadOnlyBinaryDictionary.java | 13 ++ .../latin/SingleDictionaryFacilitator.kt | 3 + .../keyboard/latin/settings/Defaults.kt | 1 + .../keyboard/latin/settings/Settings.java | 1 + .../latin/settings/SettingsValues.java | 2 + .../latin/utils/DictionaryInfoUtils.kt | 4 + .../settings/screens/PreferencesScreen.kt | 9 ++ .../main/res/layout/popup_keys_keyboard.xml | 9 ++ app/src/main/res/values/strings.xml | 4 + 21 files changed, 374 insertions(+), 99 deletions(-) create mode 100644 app/src/main/java/helium314/keyboard/keyboard/PopupTextView.java rename app/src/main/java/helium314/keyboard/keyboard/emoji/{OnKeyEventListener.java => EmojiViewCallback.java} (54%) diff --git a/app/src/main/java/helium314/keyboard/keyboard/KeyboardSwitcher.java b/app/src/main/java/helium314/keyboard/keyboard/KeyboardSwitcher.java index e4849c259..9f8796353 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/KeyboardSwitcher.java +++ b/app/src/main/java/helium314/keyboard/keyboard/KeyboardSwitcher.java @@ -138,7 +138,7 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions { return false; } - public void loadKeyboard(final EditorInfo editorInfo, final SettingsValues settingsValues, + private void loadKeyboard(final EditorInfo editorInfo, final SettingsValues settingsValues, final int currentAutoCapsState, final int currentRecapitalizeState) { final KeyboardLayoutSet.Builder builder = new KeyboardLayoutSet.Builder( mThemeContext, editorInfo); @@ -527,6 +527,10 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions { if (mCurrentInputView == null) return; mEmojiPalettesView.clearKeyboardCache(); + reloadMainKeyboard(); + } + + public void reloadMainKeyboard() { loadKeyboard(mLatinIME.getCurrentInputEditorInfo(), Settings.getValues(), mLatinIME.getCurrentAutoCapsState(), mLatinIME.getCurrentRecapitalizeState()); } diff --git a/app/src/main/java/helium314/keyboard/keyboard/PopupKeysKeyboardView.java b/app/src/main/java/helium314/keyboard/keyboard/PopupKeysKeyboardView.java index 7d1735c08..4aa543201 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/PopupKeysKeyboardView.java +++ b/app/src/main/java/helium314/keyboard/keyboard/PopupKeysKeyboardView.java @@ -12,15 +12,15 @@ import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.drawable.Drawable; import android.util.AttributeSet; +import android.view.Gravity; import android.view.MotionEvent; import android.view.View; -import android.view.ViewGroup; import androidx.annotation.NonNull; import helium314.keyboard.accessibility.AccessibilityUtils; import helium314.keyboard.accessibility.PopupKeysKeyboardAccessibilityDelegate; -import helium314.keyboard.keyboard.emoji.OnKeyEventListener; +import helium314.keyboard.keyboard.emoji.EmojiViewCallback; import helium314.keyboard.keyboard.internal.KeyDrawParams; import helium314.keyboard.keyboard.internal.keyboard_parser.floris.KeyCode; import helium314.keyboard.latin.R; @@ -39,7 +39,7 @@ public class PopupKeysKeyboardView extends KeyboardView implements PopupKeysPane protected final KeyDetector mKeyDetector; private Controller mController = EMPTY_CONTROLLER; protected KeyboardActionListener mListener; - protected OnKeyEventListener mKeyEventListener; + protected EmojiViewCallback mEmojiViewCallback; private int mOriginX; private int mOriginY; private Key mCurrentKey; @@ -122,7 +122,7 @@ public class PopupKeysKeyboardView extends KeyboardView implements PopupKeysPane public void showPopupKeysPanel(final View parentView, final Controller controller, final int pointX, final int pointY, final KeyboardActionListener listener) { mListener = listener; - mKeyEventListener = null; + mEmojiViewCallback = null; showPopupKeysPanelInternal(parentView, controller, pointX, pointY); } @@ -131,9 +131,9 @@ public class PopupKeysKeyboardView extends KeyboardView implements PopupKeysPane */ @Override public void showPopupKeysPanel(final View parentView, final Controller controller, - final int pointX, final int pointY, final OnKeyEventListener listener) { + final int pointX, final int pointY, final EmojiViewCallback emojiViewCallback) { mListener = null; - mKeyEventListener = listener; + mEmojiViewCallback = emojiViewCallback; showPopupKeysPanelInternal(parentView, controller, pointX, pointY); } @@ -157,6 +157,9 @@ public class PopupKeysKeyboardView extends KeyboardView implements PopupKeysPane mOriginX = x + container.getPaddingLeft(); mOriginY = y + container.getPaddingTop(); + var center = panelX + getMeasuredWidth() / 2; + // This is needed for cases where there's also a long text popup above this keyboard + controller.setLayoutGravity(center < pointX? Gravity.RIGHT : center > pointX? Gravity.LEFT : Gravity.CENTER_HORIZONTAL); controller.onShowPopupKeysPanel(this); final PopupKeysKeyboardAccessibilityDelegate accessibilityDelegate = mAccessibilityDelegate; if (accessibilityDelegate != null @@ -222,8 +225,8 @@ public class PopupKeysKeyboardView extends KeyboardView implements PopupKeysPane false /* isKeyRepeat */); } } - } else if (mKeyEventListener != null) { - mKeyEventListener.onReleaseKey(key); + } else if (mEmojiViewCallback != null) { + mEmojiViewCallback.onReleaseKey(key); } } @@ -314,28 +317,4 @@ public class PopupKeysKeyboardView extends KeyboardView implements PopupKeysPane } return super.onHoverEvent(event); } - - private View getContainerView() { - return (View)getParent(); - } - - @Override - public void showInParent(final ViewGroup parentView) { - removeFromParent(); - parentView.addView(getContainerView()); - } - - @Override - public void removeFromParent() { - final View containerView = getContainerView(); - final ViewGroup currentParent = (ViewGroup)containerView.getParent(); - if (currentParent != null) { - currentParent.removeView(containerView); - } - } - - @Override - public boolean isShowingInParent() { - return (getContainerView().getParent() != null); - } } diff --git a/app/src/main/java/helium314/keyboard/keyboard/PopupKeysPanel.java b/app/src/main/java/helium314/keyboard/keyboard/PopupKeysPanel.java index 5a7e60e30..b511db9d3 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/PopupKeysPanel.java +++ b/app/src/main/java/helium314/keyboard/keyboard/PopupKeysPanel.java @@ -8,10 +8,17 @@ package helium314.keyboard.keyboard; import android.view.View; import android.view.ViewGroup; -import helium314.keyboard.keyboard.emoji.OnKeyEventListener; +import helium314.keyboard.keyboard.emoji.EmojiViewCallback; public interface PopupKeysPanel { interface Controller { + /** + * Set the layout gravity. + * @param layoutGravity requested by the popup + */ + default void setLayoutGravity(int layoutGravity) { + } + /** * Add the {@link PopupKeysPanel} to the target view. * @param panel the panel to be shown. @@ -59,19 +66,18 @@ public interface PopupKeysPanel { * Initializes the layout and event handling of this {@link PopupKeysPanel} and calls the * controller's onShowPopupKeysPanel to add the panel's container view. * Same as {@link PopupKeysPanel#showPopupKeysPanel(View, Controller, int, int, KeyboardActionListener)}, - * but with a {@link OnKeyEventListener}. + * but with a {@link EmojiViewCallback}. * * @param parentView the parent view of this {@link PopupKeysPanel} * @param controller the controller that can dismiss this {@link PopupKeysPanel} * @param pointX x coordinate of this {@link PopupKeysPanel} * @param pointY y coordinate of this {@link PopupKeysPanel} - * @param listener the listener that will receive keyboard action from this - * {@link PopupKeysPanel}. + * @param emojiViewCallback to receive keyboard actions from this {@link PopupKeysPanel}. */ // TODO: Currently the PopupKeysPanel is inside a container view that is added to the parent. // Consider the simpler approach of placing the PopupKeysPanel itself into the parent view. void showPopupKeysPanel(View parentView, Controller controller, int pointX, - int pointY, OnKeyEventListener listener); + int pointY, EmojiViewCallback emojiViewCallback); /** * Dismisses the popup keys panel and calls the controller's onDismissPopupKeysPanel to remove @@ -127,20 +133,35 @@ public interface PopupKeysPanel { */ int translateY(int y); + default View getContainerView() { + return (View) ((View) this).getParent(); + } + /** * Show this {@link PopupKeysPanel} in the parent view. * * @param parentView the {@link ViewGroup} that hosts this {@link PopupKeysPanel}. */ - void showInParent(ViewGroup parentView); + default void showInParent(ViewGroup parentView) { + removeFromParent(); + parentView.addView(getContainerView()); + } /** * Remove this {@link PopupKeysPanel} from the parent view. */ - void removeFromParent(); + default void removeFromParent() { + final View containerView = getContainerView(); + final ViewGroup currentParent = (ViewGroup)containerView.getParent(); + if (currentParent != null) { + currentParent.removeView(containerView); + } + } /** * Return whether the panel is currently being shown. */ - boolean isShowingInParent(); + default boolean isShowingInParent() { + return getContainerView().getParent() != null; + } } diff --git a/app/src/main/java/helium314/keyboard/keyboard/PopupTextView.java b/app/src/main/java/helium314/keyboard/keyboard/PopupTextView.java new file mode 100644 index 000000000..d20f62559 --- /dev/null +++ b/app/src/main/java/helium314/keyboard/keyboard/PopupTextView.java @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * modified + * SPDX-License-Identifier: Apache-2.0 AND GPL-3.0-only + */ + +package helium314.keyboard.keyboard; + +import android.content.Context; +import android.graphics.Typeface; +import android.util.AttributeSet; +import android.util.TypedValue; +import android.view.Gravity; +import android.view.View; +import android.widget.TextView; +import helium314.keyboard.keyboard.emoji.EmojiViewCallback; +import helium314.keyboard.keyboard.internal.KeyDrawParams; +import helium314.keyboard.latin.R; +import helium314.keyboard.latin.common.ColorType; +import helium314.keyboard.latin.common.CoordinateUtils; +import helium314.keyboard.latin.settings.Settings; + + +/** + * A view that displays popup text. + */ +public class PopupTextView extends TextView implements PopupKeysPanel { + private final int[] mCoordinates = CoordinateUtils.newInstance(); + private final Typeface mTypeface; + private Controller mController = EMPTY_CONTROLLER; + private int mOriginX; + private int mOriginY; + private Key mKey; + private EmojiViewCallback mEmojiViewCallback; + + public PopupTextView(final Context context, final AttributeSet attrs) { + this(context, attrs, R.attr.popupKeysKeyboardViewStyle); + } + + public PopupTextView(final Context context, final AttributeSet attrs, + final int defStyle) { + super(context, attrs, defStyle); + mTypeface = Settings.getInstance().getCustomTypeface(); + } + + public void setKeyDrawParams(Key key, KeyDrawParams drawParams) { + mKey = key; + Settings.getValues().mColors.setBackground(this, ColorType.KEY_PREVIEW_BACKGROUND); + setTextColor(drawParams.mPreviewTextColor); + setTextSize(TypedValue.COMPLEX_UNIT_PX, key.selectHintTextSize(drawParams) << 1); + setTypeface(mTypeface == null ? key.selectTypeface(drawParams) : mTypeface); + } + + @Override + public void showPopupKeysPanel(final View parentView, final Controller controller, + final int pointX, final int pointY, final KeyboardActionListener listener) { + showPopupKeysPanelInternal(parentView, controller, pointX, pointY); + } + + @Override + public void showPopupKeysPanel(final View parentView, final Controller controller, + final int pointX, final int pointY, final EmojiViewCallback emojiViewCallback) { + mEmojiViewCallback = emojiViewCallback; + showPopupKeysPanelInternal(parentView, controller, pointX, pointY); + } + + private void showPopupKeysPanelInternal(final View parentView, final Controller controller, + final int pointX, final int pointY) { + mController = controller; + final View container = getContainerView(); + // The coordinates of panel's left-top corner in parentView's coordinate system. + // We need to consider background drawable paddings. + final int x = pointX - getMeasuredWidth() / 2 - container.getPaddingLeft() - getPaddingLeft(); + final int y = pointY - container.getMeasuredHeight() + container.getPaddingBottom() + + getPaddingBottom(); + + parentView.getLocationInWindow(mCoordinates); + // Ensure the horizontal position of the panel does not extend past the parentView edges. + final int maxX = parentView.getMeasuredWidth() - container.getMeasuredWidth(); + final int panelX = Math.max(0, Math.min(maxX, x)) + CoordinateUtils.x(mCoordinates); + final int panelY = y + CoordinateUtils.y(mCoordinates); + container.setX(panelX); + container.setY(panelY); + + mOriginX = x + container.getPaddingLeft(); + mOriginY = y + container.getPaddingTop(); + controller.setLayoutGravity(Gravity.NO_GRAVITY); + controller.onShowPopupKeysPanel(this); + } + + @Override + public void onDownEvent(final int x, final int y, final int pointerId, final long eventTime) { + } + + @Override + public void onMoveEvent(final int x, final int y, final int pointerId, final long eventTime) { + } + + @Override + public void onUpEvent(final int x, final int y, final int pointerId, final long eventTime) { + mEmojiViewCallback.onReleaseKey(mKey); + } + + @Override + public void dismissPopupKeysPanel() { + if (!isShowingInParent()) { + return; + } + mController.onDismissPopupKeysPanel(); + } + + @Override + public int translateX(final int x) { + return x - mOriginX; + } + + @Override + public int translateY(final int y) { + return y - mOriginY; + } +} diff --git a/app/src/main/java/helium314/keyboard/keyboard/emoji/EmojiPageKeyboardView.java b/app/src/main/java/helium314/keyboard/keyboard/emoji/EmojiPageKeyboardView.java index 47e0d3b83..cc1e0a359 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/emoji/EmojiPageKeyboardView.java +++ b/app/src/main/java/helium314/keyboard/keyboard/emoji/EmojiPageKeyboardView.java @@ -13,6 +13,9 @@ import android.graphics.PorterDuff; import android.graphics.PorterDuffXfermode; import android.os.Handler; import android.util.AttributeSet; +import android.view.Gravity; +import android.widget.LinearLayout; +import helium314.keyboard.keyboard.PopupTextView; import helium314.keyboard.latin.utils.Log; import android.view.LayoutInflater; import android.view.MotionEvent; @@ -53,14 +56,18 @@ public final class EmojiPageKeyboardView extends KeyboardView implements private static final long KEY_PRESS_DELAY_TIME = 250; // msec private static final long KEY_RELEASE_DELAY_TIME = 30; // msec - private static final OnKeyEventListener EMPTY_LISTENER = new OnKeyEventListener() { + private static final EmojiViewCallback EMPTY_EMOJI_VIEW_CALLBACK = new EmojiViewCallback() { @Override public void onPressKey(final Key key) {} @Override public void onReleaseKey(final Key key) {} + @Override + public String getDescription(String emoji) { + return null; + } }; - private OnKeyEventListener mListener = EMPTY_LISTENER; + private EmojiViewCallback mEmojiViewCallback = EMPTY_EMOJI_VIEW_CALLBACK; private final KeyDetector mKeyDetector = new KeyDetector(); private KeyboardAccessibilityDelegate mAccessibilityDelegate; @@ -74,6 +81,8 @@ public final class EmojiPageKeyboardView extends KeyboardView implements // More keys keyboard private final View mPopupKeysKeyboardContainer; + private final PopupTextView mDescriptionView; + private final PopupKeysKeyboardView mPopupKeysKeyboardView; private final WeakHashMap mPopupKeysKeyboardCache = new WeakHashMap<>(); private final boolean mConfigShowPopupKeysKeyboardAtTouchedPoint; private final ViewGroup mPopupKeysPlacerView; @@ -102,6 +111,8 @@ public final class EmojiPageKeyboardView extends KeyboardView implements final LayoutInflater inflater = LayoutInflater.from(getContext()); mPopupKeysKeyboardContainer = inflater.inflate(popupKeysKeyboardLayoutId, null); + mDescriptionView = mPopupKeysKeyboardContainer.findViewById(R.id.description_view); + mPopupKeysKeyboardView = mPopupKeysKeyboardContainer.findViewById(R.id.popup_keys_keyboard_view); } @Override @@ -146,8 +157,8 @@ public final class EmojiPageKeyboardView extends KeyboardView implements } } - public void setOnKeyEventListener(final OnKeyEventListener listener) { - mListener = listener; + public void setEmojiViewCallback(final EmojiViewCallback emojiViewCallback) { + mEmojiViewCallback = emojiViewCallback; } /** @@ -169,7 +180,8 @@ public final class EmojiPageKeyboardView extends KeyboardView implements } @Nullable - public PopupKeysPanel showPopupKeysKeyboard(@NonNull final Key key, final int lastX, final int lastY) { + private PopupKeysPanel showPopupKeysKeyboard(@NonNull final Key key) { + mPopupKeysKeyboardView.setVisibility(GONE); final PopupKeySpec[] popupKeys = key.getPopupKeys(); if (popupKeys == null) { return null; @@ -182,21 +194,9 @@ public final class EmojiPageKeyboardView extends KeyboardView implements mPopupKeysKeyboardCache.put(key, popupKeysKeyboard); } - final View container = mPopupKeysKeyboardContainer; - final PopupKeysKeyboardView popupKeysKeyboardView = container.findViewById(R.id.popup_keys_keyboard_view); - popupKeysKeyboardView.setKeyboard(popupKeysKeyboard); - container.measure(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); - - final int[] lastCoords = CoordinateUtils.newCoordinateArray(1, lastX, lastY); - // The popup keys keyboard is usually horizontally aligned with the center of the parent key. - // If showPopupKeysKeyboardAtTouchedPoint is true and the key preview is disabled, the more - // keys keyboard is placed at the touch point of the parent key. - final int pointX = mConfigShowPopupKeysKeyboardAtTouchedPoint - ? CoordinateUtils.x(lastCoords) - : key.getX() + key.getWidth() / 2; - final int pointY = key.getY(); - popupKeysKeyboardView.showPopupKeysPanel(this, this, pointX, pointY, mListener); - return popupKeysKeyboardView; + mPopupKeysKeyboardView.setKeyboard(popupKeysKeyboard); + mPopupKeysKeyboardView.setVisibility(VISIBLE); + return mPopupKeysKeyboardView; } private void dismissPopupKeysPanel() { @@ -209,6 +209,17 @@ public final class EmojiPageKeyboardView extends KeyboardView implements return mPopupKeysPanel != null; } + @Override + public void setLayoutGravity(int layoutGravity) { + var layoutParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.WRAP_CONTENT); + layoutParams.gravity = mDescriptionView.getMeasuredWidth() > mPopupKeysKeyboardView.getMeasuredWidth()? + layoutGravity : Gravity.CENTER_HORIZONTAL; + mPopupKeysKeyboardContainer.setLayoutParams(layoutParams); + mDescriptionView.setLayoutParams(layoutParams); + mPopupKeysKeyboardView.setLayoutParams(layoutParams); + } + @Override public void onShowPopupKeysPanel(final PopupKeysPanel panel) { // install placer view only when needed instead of when this @@ -290,9 +301,11 @@ public final class EmojiPageKeyboardView extends KeyboardView implements return; } + var descriptionPanel = showDescription(key); + final PopupKeysPanel popupKeysPanel = showPopupKeysKeyboard(key); + final int x = mLastX; final int y = mLastY; - final PopupKeysPanel popupKeysPanel = showPopupKeysKeyboard(key, x, y); if (popupKeysPanel != null) { final int translatedX = popupKeysPanel.translateX(x); final int translatedY = popupKeysPanel.translateY(y); @@ -301,6 +314,34 @@ public final class EmojiPageKeyboardView extends KeyboardView implements // want any scroll to append during this entire input. disallowParentInterceptTouchEvent(true); } + + if (popupKeysPanel != null || descriptionPanel != null) { + mPopupKeysKeyboardContainer.measure(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); + + final int[] lastCoords = CoordinateUtils.newCoordinateArray(1, x, y); + // The popup keys keyboard is usually horizontally aligned with the center of the parent key. + // If showPopupKeysKeyboardAtTouchedPoint is true and the key preview is disabled, the more + // keys keyboard is placed at the touch point of the parent key. + final int pointX = mConfigShowPopupKeysKeyboardAtTouchedPoint + ? CoordinateUtils.x(lastCoords) + : key.getX() + key.getWidth() / 2; + final int pointY = key.getY() - getKeyboard().mVerticalGap; + (popupKeysPanel != null? popupKeysPanel : descriptionPanel) + .showPopupKeysPanel(this, this, pointX, pointY, mEmojiViewCallback); + } + } + + private PopupKeysPanel showDescription(Key key) { + mDescriptionView.setVisibility(GONE); + var description = mEmojiViewCallback.getDescription(key.getLabel()); + if (description == null) { + return null; + } + + mDescriptionView.setText(description); + mDescriptionView.setKeyDrawParams(key, getKeyDrawParams()); + mDescriptionView.setVisibility(VISIBLE); + return mDescriptionView; } private void registerPress(final Key key) { @@ -318,7 +359,7 @@ public final class EmojiPageKeyboardView extends KeyboardView implements releasedKey.onReleased(); invalidateKey(releasedKey); if (withKeyRegistering) { - mListener.onReleaseKey(releasedKey); + mEmojiViewCallback.onReleaseKey(releasedKey); } } @@ -326,7 +367,7 @@ public final class EmojiPageKeyboardView extends KeyboardView implements mPendingKeyDown = null; pressedKey.onPressed(); invalidateKey(pressedKey); - mListener.onPressKey(pressedKey); + mEmojiViewCallback.onPressKey(pressedKey); } public void releaseCurrentKey(final boolean withKeyRegistering) { diff --git a/app/src/main/java/helium314/keyboard/keyboard/emoji/EmojiPalettesAdapter.java b/app/src/main/java/helium314/keyboard/keyboard/emoji/EmojiPalettesAdapter.java index 7f7b63614..4172d3db9 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/emoji/EmojiPalettesAdapter.java +++ b/app/src/main/java/helium314/keyboard/keyboard/emoji/EmojiPalettesAdapter.java @@ -7,7 +7,6 @@ package helium314.keyboard.keyboard.emoji; import helium314.keyboard.latin.utils.Log; -import android.util.SparseArray; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -23,13 +22,13 @@ final class EmojiPalettesAdapter extends RecyclerView.Adapter, locale: Locale) { - if (!file.isFile) return - val header = DictionaryInfoUtils.getDictionaryFileHeaderOrNull(file) ?: return killDictionary(file) + val dictionary = getDictionary(file, locale) ?: return + if (dicts.any { it.mDictType == dictionary.mDictType }) { + dictionary.close() + return + } + dicts.add(dictionary) + } + + @JvmStatic + fun getDictionary( + file: File, + locale: Locale + ): Dictionary? { + if (!file.isFile) return null + val header = DictionaryInfoUtils.getDictionaryFileHeaderOrNull(file) + if (header == null) { + killDictionary(file) + return null + } val dictType = header.mIdString.split(":").first() - if (dicts.any { it.mDictType == dictType }) return val readOnlyBinaryDictionary = ReadOnlyBinaryDictionary( file.absolutePath, 0, file.length(), false, locale, dictType ) @@ -75,14 +91,13 @@ object DictionaryFactory { if (readOnlyBinaryDictionary.isValidDictionary) { if (locale.language == "ko") { // Use KoreanDictionary for Korean locale - dicts.add(KoreanDictionary(readOnlyBinaryDictionary)) - } else { - dicts.add(readOnlyBinaryDictionary) + return KoreanDictionary(readOnlyBinaryDictionary) } - } else { - readOnlyBinaryDictionary.close() - killDictionary(file) + return readOnlyBinaryDictionary } + readOnlyBinaryDictionary.close() + killDictionary(file) + return null } private fun killDictionary(file: File) { diff --git a/app/src/main/java/helium314/keyboard/latin/KoreanDictionary.java b/app/src/main/java/helium314/keyboard/latin/KoreanDictionary.java index 9b791148c..2b6722777 100644 --- a/app/src/main/java/helium314/keyboard/latin/KoreanDictionary.java +++ b/app/src/main/java/helium314/keyboard/latin/KoreanDictionary.java @@ -4,6 +4,7 @@ package helium314.keyboard.latin; import helium314.keyboard.event.HangulCombiner; import helium314.keyboard.latin.common.ComposedData; +import helium314.keyboard.latin.makedict.WordProperty; import helium314.keyboard.latin.settings.SettingsValuesForSuggestion; import java.text.Normalizer; @@ -72,6 +73,11 @@ public class KoreanDictionary extends Dictionary { return mDictionary.getMaxFrequencyOfExactMatches(processInput(word)); } + @Override + public WordProperty getWordProperty(String word, boolean isBeginningOfSentence) { + return mDictionary.getWordProperty(processInput(word), isBeginningOfSentence); + } + @Override protected boolean same(char[] word, int length, String typedWord) { word = processInput(new String(word)).toCharArray(); diff --git a/app/src/main/java/helium314/keyboard/latin/LatinIME.java b/app/src/main/java/helium314/keyboard/latin/LatinIME.java index 670e820bd..1f7a6221c 100644 --- a/app/src/main/java/helium314/keyboard/latin/LatinIME.java +++ b/app/src/main/java/helium314/keyboard/latin/LatinIME.java @@ -279,9 +279,7 @@ public class LatinIME extends InputMethodService implements msg.arg2 /* remainingTries */, this /* handler */)) { // If we were able to reset the caches, then we can reload the keyboard. // Otherwise, we'll do it when we can. - latinIme.mKeyboardSwitcher.loadKeyboard(latinIme.getCurrentInputEditorInfo(), - settingsValues, latinIme.getCurrentAutoCapsState(), - latinIme.getCurrentRecapitalizeState()); + latinIme.mKeyboardSwitcher.reloadMainKeyboard(); } break; case MSG_WAIT_FOR_DICTIONARY_LOAD: @@ -1071,7 +1069,7 @@ public class LatinIME extends InputMethodService implements if (isDifferentTextField) { mainKeyboardView.closing(); suggest.setAutoCorrectionThreshold(currentSettingsValues.mAutoCorrectionThreshold); - switcher.loadKeyboard(editorInfo, currentSettingsValues, getCurrentAutoCapsState(), getCurrentRecapitalizeState()); + switcher.reloadMainKeyboard(); if (needToCallLoadKeyboardLater) { // If we need to call loadKeyboard again later, we need to save its state now. The // later call will be done in #retryResetCaches. @@ -1763,8 +1761,7 @@ public class LatinIME extends InputMethodService implements loadSettings(); if (mKeyboardSwitcher.getMainKeyboardView() != null) { // Reload keyboard because the current language has been changed. - mKeyboardSwitcher.loadKeyboard(getCurrentInputEditorInfo(), mSettings.getCurrent(), - getCurrentAutoCapsState(), getCurrentRecapitalizeState()); + mKeyboardSwitcher.reloadMainKeyboard(); } } diff --git a/app/src/main/java/helium314/keyboard/latin/ReadOnlyBinaryDictionary.java b/app/src/main/java/helium314/keyboard/latin/ReadOnlyBinaryDictionary.java index b4cfe5f3a..f757e87eb 100644 --- a/app/src/main/java/helium314/keyboard/latin/ReadOnlyBinaryDictionary.java +++ b/app/src/main/java/helium314/keyboard/latin/ReadOnlyBinaryDictionary.java @@ -10,6 +10,7 @@ import com.android.inputmethod.latin.BinaryDictionary; import helium314.keyboard.latin.SuggestedWords.SuggestedWordInfo; import helium314.keyboard.latin.common.ComposedData; +import helium314.keyboard.latin.makedict.WordProperty; import helium314.keyboard.latin.settings.SettingsValuesForSuggestion; import java.util.ArrayList; @@ -107,6 +108,18 @@ public final class ReadOnlyBinaryDictionary extends Dictionary { return NOT_A_PROBABILITY; } + @Override + public WordProperty getWordProperty(String word, boolean isBeginningOfSentence) { + if (mLock.readLock().tryLock()) { + try { + return mBinaryDictionary.getWordProperty(word, isBeginningOfSentence); + } finally { + mLock.readLock().unlock(); + } + } + return null; + } + @Override public void close() { mLock.writeLock().lock(); diff --git a/app/src/main/java/helium314/keyboard/latin/SingleDictionaryFacilitator.kt b/app/src/main/java/helium314/keyboard/latin/SingleDictionaryFacilitator.kt index e8941c459..4e2d62486 100644 --- a/app/src/main/java/helium314/keyboard/latin/SingleDictionaryFacilitator.kt +++ b/app/src/main/java/helium314/keyboard/latin/SingleDictionaryFacilitator.kt @@ -7,6 +7,7 @@ import helium314.keyboard.keyboard.Keyboard import helium314.keyboard.keyboard.KeyboardSwitcher import helium314.keyboard.latin.DictionaryFacilitator.DictionaryInitializationListener import helium314.keyboard.latin.common.ComposedData +import helium314.keyboard.latin.makedict.WordProperty import helium314.keyboard.latin.settings.SettingsValuesForSuggestion import helium314.keyboard.latin.utils.SuggestionResults import java.util.Locale @@ -47,6 +48,8 @@ class SingleDictionaryFacilitator(private val dict: Dictionary) : DictionaryFaci return suggestionResults } + fun getWordProperty(word: String): WordProperty? = dict.getWordProperty(word, false) + // ------------ dummy functionality ---------------- override fun setValidSpellingWordReadCache(cache: LruCache) {} 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 98e93a64f..a73760205 100644 --- a/app/src/main/java/helium314/keyboard/latin/settings/Defaults.kt +++ b/app/src/main/java/helium314/keyboard/latin/settings/Defaults.kt @@ -58,6 +58,7 @@ object Defaults { const val PREF_VIBRATE_ON = false const val PREF_VIBRATE_IN_DND_MODE = false const val PREF_SOUND_ON = false + const val PREF_SHOW_EMOJI_DESCRIPTIONS = true @JvmField var PREF_POPUP_ON = true const val PREF_AUTO_CORRECTION = true 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 ea6e8f59f..cc72c893c 100644 --- a/app/src/main/java/helium314/keyboard/latin/settings/Settings.java +++ b/app/src/main/java/helium314/keyboard/latin/settings/Settings.java @@ -67,6 +67,7 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang public static final String PREF_VIBRATE_ON = "vibrate_on"; public static final String PREF_VIBRATE_IN_DND_MODE = "vibrate_in_dnd_mode"; public static final String PREF_SOUND_ON = "sound_on"; + public static final String PREF_SHOW_EMOJI_DESCRIPTIONS = "show_emoji_descriptions"; public static final String PREF_POPUP_ON = "popup_on"; public static final String PREF_AUTO_CORRECTION = "auto_correction"; public static final String PREF_MORE_AUTO_CORRECTION = "more_auto_correction"; diff --git a/app/src/main/java/helium314/keyboard/latin/settings/SettingsValues.java b/app/src/main/java/helium314/keyboard/latin/settings/SettingsValues.java index 689a63700..f750f1072 100644 --- a/app/src/main/java/helium314/keyboard/latin/settings/SettingsValues.java +++ b/app/src/main/java/helium314/keyboard/latin/settings/SettingsValues.java @@ -54,6 +54,7 @@ public class SettingsValues { public final boolean mVibrateOn; public final boolean mVibrateInDndMode; public final boolean mSoundOn; + public final boolean mShowEmojiDescriptions; public final boolean mKeyPreviewPopupOn; public final boolean mShowsVoiceInputKey; public final boolean mLanguageSwitchKeyToOtherImes; @@ -169,6 +170,7 @@ public class SettingsValues { mVibrateOn = Settings.readVibrationEnabled(prefs); mVibrateInDndMode = prefs.getBoolean(Settings.PREF_VIBRATE_IN_DND_MODE, Defaults.PREF_VIBRATE_IN_DND_MODE); mSoundOn = prefs.getBoolean(Settings.PREF_SOUND_ON, Defaults.PREF_SOUND_ON); + mShowEmojiDescriptions = prefs.getBoolean(Settings.PREF_SHOW_EMOJI_DESCRIPTIONS, Defaults.PREF_SHOW_EMOJI_DESCRIPTIONS); mKeyPreviewPopupOn = prefs.getBoolean(Settings.PREF_POPUP_ON, Defaults.PREF_POPUP_ON); mSlidingKeyInputPreviewEnabled = prefs.getBoolean( DebugSettings.PREF_SLIDING_KEY_INPUT_PREVIEW, Defaults.PREF_SLIDING_KEY_INPUT_PREVIEW); diff --git a/app/src/main/java/helium314/keyboard/latin/utils/DictionaryInfoUtils.kt b/app/src/main/java/helium314/keyboard/latin/utils/DictionaryInfoUtils.kt index 8715d2d4c..05417fdb3 100644 --- a/app/src/main/java/helium314/keyboard/latin/utils/DictionaryInfoUtils.kt +++ b/app/src/main/java/helium314/keyboard/latin/utils/DictionaryInfoUtils.kt @@ -101,6 +101,10 @@ object DictionaryInfoUtils { return absoluteDirectoryName } + @JvmStatic + fun getCachedDictForLocaleAndType(locale: Locale, type: String, context: Context): File? = + getCachedDictsForLocale(locale, context).firstOrNull { it.name.substringBefore("_") == type } + fun getCachedDictsForLocale(locale: Locale, context: Context) = getCacheDirectoryForLocale(locale, context)?.let { File(it).listFiles() }.orEmpty() diff --git a/app/src/main/java/helium314/keyboard/settings/screens/PreferencesScreen.kt b/app/src/main/java/helium314/keyboard/settings/screens/PreferencesScreen.kt index 272757201..c41696059 100644 --- a/app/src/main/java/helium314/keyboard/settings/screens/PreferencesScreen.kt +++ b/app/src/main/java/helium314/keyboard/settings/screens/PreferencesScreen.kt @@ -57,6 +57,7 @@ fun PreferencesScreen( Settings.PREF_SOUND_ON, if (prefs.getBoolean(Settings.PREF_SOUND_ON, Defaults.PREF_SOUND_ON)) Settings.PREF_KEYPRESS_SOUND_VOLUME else null, + Settings.PREF_SHOW_EMOJI_DESCRIPTIONS, R.string.settings_category_additional_keys, Settings.PREF_SHOW_NUMBER_ROW, if (SubtypeSettings.getEnabledSubtypes(true).any { it.locale().language in localesWithLocalizedNumberRow }) @@ -111,6 +112,14 @@ fun createPreferencesSettings(context: Context) = listOf( Setting(context, Settings.PREF_SOUND_ON, R.string.sound_on_keypress) { SwitchPreference(it, Defaults.PREF_SOUND_ON) }, + Setting( + context, Settings.PREF_SHOW_EMOJI_DESCRIPTIONS, R.string.show_emoji_descriptions, + R.string.show_emoji_descriptions_summary + ) { + SwitchPreference(it, Defaults.PREF_SHOW_EMOJI_DESCRIPTIONS) { + KeyboardSwitcher.getInstance().reloadKeyboard() + } + }, Setting(context, Settings.PREF_ENABLE_CLIPBOARD_HISTORY, R.string.enable_clipboard_history, R.string.enable_clipboard_history_summary) { diff --git a/app/src/main/res/layout/popup_keys_keyboard.xml b/app/src/main/res/layout/popup_keys_keyboard.xml index a8bea57c4..e9e249482 100644 --- a/app/src/main/res/layout/popup_keys_keyboard.xml +++ b/app/src/main/res/layout/popup_keys_keyboard.xml @@ -10,6 +10,15 @@ android:layout_height="wrap_content" android:orientation="vertical" > + Vibrate in do not disturb mode Sound on keypress + + Show emoji description on long press + + Requires an emoji dictionary Popup on keypress From f06a553d2c5171de1401e7202af1e86b517794cf Mon Sep 17 00:00:00 2001 From: Helium314 Date: Fri, 13 Jun 2025 20:49:53 +0200 Subject: [PATCH 05/26] make sure selection start is before end this should not be necessary according to documentation, but apparently there are apps (even by Google!) that can't deal with documented behavior see comments in GH-1512 --- .../helium314/keyboard/latin/RichInputConnection.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/helium314/keyboard/latin/RichInputConnection.java b/app/src/main/java/helium314/keyboard/latin/RichInputConnection.java index 95beb8683..7d34df49b 100644 --- a/app/src/main/java/helium314/keyboard/latin/RichInputConnection.java +++ b/app/src/main/java/helium314/keyboard/latin/RichInputConnection.java @@ -716,8 +716,13 @@ public final class RichInputConnection implements PrivateCommandPerformer { if (start < 0 || end < 0) { return false; } - mExpectedSelStart = start; - mExpectedSelEnd = end; + if (start > end) { + mExpectedSelStart = end; + mExpectedSelEnd = start; + } else { + mExpectedSelStart = start; + mExpectedSelEnd = end; + } if (isConnected()) { final boolean isIcValid = mIC.setSelection(start, end); if (!isIcValid) { From 63dad1549ec411cf54ac717639e909321f70541e Mon Sep 17 00:00:00 2001 From: Helium314 Date: Fri, 13 Jun 2025 20:57:49 +0200 Subject: [PATCH 06/26] redirect suggestion strip view code input to KeyboardActionListener --- app/src/main/java/helium314/keyboard/latin/LatinIME.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/helium314/keyboard/latin/LatinIME.java b/app/src/main/java/helium314/keyboard/latin/LatinIME.java index 1f7a6221c..a7701684d 100644 --- a/app/src/main/java/helium314/keyboard/latin/LatinIME.java +++ b/app/src/main/java/helium314/keyboard/latin/LatinIME.java @@ -1537,9 +1537,10 @@ public class LatinIME extends InputMethodService implements // Implementation of {@link SuggestionStripView.Listener}. @Override public void onCodeInput(final int codePoint, final int x, final int y, final boolean isKeyRepeat) { - onCodeInput(codePoint, 0, x, y, isKeyRepeat); + mKeyboardActionListener.onCodeInput(codePoint, x, y, isKeyRepeat); } + // called by KeyboardActionListener public void onCodeInput(final int codePoint, final int metaState, final int x, final int y, final boolean isKeyRepeat) { if (codePoint < 0) { switch (codePoint) { From a37de668c05a957ba8546b4659d8d61695fda258 Mon Sep 17 00:00:00 2001 From: Helium314 Date: Sat, 14 Jun 2025 11:29:26 +0200 Subject: [PATCH 07/26] enable hardware keyboard handling again, with improved handling --- .../java/helium314/keyboard/event/Event.kt | 4 +- .../event/HardwareKeyboardEventDecoder.kt | 4 +- .../keyboard_parser/floris/KeyCode.kt | 124 +++++++++--------- .../keyboard/latin/define/ProductionFlags.kt | 8 +- .../keyboard/latin/inputlogic/InputLogic.java | 33 +++-- 5 files changed, 86 insertions(+), 87 deletions(-) diff --git a/app/src/main/java/helium314/keyboard/event/Event.kt b/app/src/main/java/helium314/keyboard/event/Event.kt index 75fda0535..5903b0c9f 100644 --- a/app/src/main/java/helium314/keyboard/event/Event.kt +++ b/app/src/main/java/helium314/keyboard/event/Event.kt @@ -256,10 +256,8 @@ class Event private constructor( source.mX, source.mY, source.mSuggestedWordInfo, source.mFlags or FLAG_COMBINING, source.mNextEvent) } - fun createNotHandledEvent(): Event { - return Event(EVENT_TYPE_NOT_HANDLED, null, NOT_A_CODE_POINT, NOT_A_KEY_CODE, 0, + val notHandledEvent = Event(EVENT_TYPE_NOT_HANDLED, null, NOT_A_CODE_POINT, NOT_A_KEY_CODE, 0, Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE, null, FLAG_NONE, null) - } } // This method is private - to create a new event, use one of the create* utility methods. diff --git a/app/src/main/java/helium314/keyboard/event/HardwareKeyboardEventDecoder.kt b/app/src/main/java/helium314/keyboard/event/HardwareKeyboardEventDecoder.kt index 2742ee193..45dcc27b4 100644 --- a/app/src/main/java/helium314/keyboard/event/HardwareKeyboardEventDecoder.kt +++ b/app/src/main/java/helium314/keyboard/event/HardwareKeyboardEventDecoder.kt @@ -48,6 +48,8 @@ class HardwareKeyboardEventDecoder(val mDeviceId: Int) : HardwareEventDecoder { } else Event.createHardwareKeypressEvent(codePointAndFlags, keyCode, metaState, null, isKeyRepeat) // If not Enter, then this is just a regular keypress event for a normal character // that can be committed right away, taking into account the current state. - } else Event.createNotHandledEvent() + } else { + Event.notHandledEvent + } } } diff --git a/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/floris/KeyCode.kt b/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/floris/KeyCode.kt index 1488faaf5..cf34b4228 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/floris/KeyCode.kt +++ b/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/floris/KeyCode.kt @@ -210,66 +210,11 @@ object KeyCode { // todo: there are many more keys, see near https://developer.android.com/reference/android/view/KeyEvent#KEYCODE_0 /** - * Convert a keyCode / codePoint to a KeyEvent.KEYCODE_. - * Fallback to KeyEvent.KEYCODE_UNKNOWN. + * Convert an internal keyCode to a KeyEvent.KEYCODE_. + * Positive codes are passed through, unknown negative codes result in KeyEvent.KEYCODE_UNKNOWN. * To be uses for fake hardware key press. - * */ - fun Int.toKeyEventCode(): Int = if (this > 0) - when (this.toChar().uppercaseChar()) { - '/' -> KeyEvent.KEYCODE_SLASH - '\\' -> KeyEvent.KEYCODE_BACKSLASH - ';' -> KeyEvent.KEYCODE_SEMICOLON - ',' -> KeyEvent.KEYCODE_COMMA - '.' -> KeyEvent.KEYCODE_PERIOD - '\'' -> KeyEvent.KEYCODE_APOSTROPHE - '`' -> KeyEvent.KEYCODE_GRAVE - '*' -> KeyEvent.KEYCODE_STAR - ']' -> KeyEvent.KEYCODE_RIGHT_BRACKET - '[' -> KeyEvent.KEYCODE_LEFT_BRACKET - '+' -> KeyEvent.KEYCODE_PLUS - '-' -> KeyEvent.KEYCODE_MINUS - '=' -> KeyEvent.KEYCODE_EQUALS - '\n' -> KeyEvent.KEYCODE_ENTER - '\t' -> KeyEvent.KEYCODE_TAB - '0' -> KeyEvent.KEYCODE_0 - '1' -> KeyEvent.KEYCODE_1 - '2' -> KeyEvent.KEYCODE_2 - '3' -> KeyEvent.KEYCODE_3 - '4' -> KeyEvent.KEYCODE_4 - '5' -> KeyEvent.KEYCODE_5 - '6' -> KeyEvent.KEYCODE_6 - '7' -> KeyEvent.KEYCODE_7 - '8' -> KeyEvent.KEYCODE_8 - '9' -> KeyEvent.KEYCODE_9 - 'A' -> KeyEvent.KEYCODE_A - 'B' -> KeyEvent.KEYCODE_B - 'C' -> KeyEvent.KEYCODE_C - 'D' -> KeyEvent.KEYCODE_D - 'E' -> KeyEvent.KEYCODE_E - 'F' -> KeyEvent.KEYCODE_F - 'G' -> KeyEvent.KEYCODE_G - 'H' -> KeyEvent.KEYCODE_H - 'I' -> KeyEvent.KEYCODE_I - 'J' -> KeyEvent.KEYCODE_J - 'K' -> KeyEvent.KEYCODE_K - 'L' -> KeyEvent.KEYCODE_L - 'M' -> KeyEvent.KEYCODE_M - 'N' -> KeyEvent.KEYCODE_N - 'O' -> KeyEvent.KEYCODE_O - 'P' -> KeyEvent.KEYCODE_P - 'Q' -> KeyEvent.KEYCODE_Q - 'R' -> KeyEvent.KEYCODE_R - 'S' -> KeyEvent.KEYCODE_S - 'T' -> KeyEvent.KEYCODE_T - 'U' -> KeyEvent.KEYCODE_U - 'V' -> KeyEvent.KEYCODE_V - 'W' -> KeyEvent.KEYCODE_W - 'X' -> KeyEvent.KEYCODE_X - 'Y' -> KeyEvent.KEYCODE_Y - 'Z' -> KeyEvent.KEYCODE_Z - else -> KeyEvent.KEYCODE_UNKNOWN - } - else when (this) { + */ + @JvmStatic fun keyCodeToKeyEventCode(keyCode: Int) = when (keyCode) { ARROW_UP -> KeyEvent.KEYCODE_DPAD_UP ARROW_RIGHT -> KeyEvent.KEYCODE_DPAD_RIGHT ARROW_DOWN -> KeyEvent.KEYCODE_DPAD_DOWN @@ -303,6 +248,67 @@ object KeyCode { F10 -> KeyEvent.KEYCODE_F10 F11 -> KeyEvent.KEYCODE_F11 F12 -> KeyEvent.KEYCODE_F12 + else -> if (keyCode < 0) KeyEvent.KEYCODE_UNKNOWN else keyCode + } + + // todo: there are many more keys, see near https://developer.android.com/reference/android/view/KeyEvent#KEYCODE_0 + /** + * Convert a codePoint to a KeyEvent.KEYCODE_. + * Fallback to KeyEvent.KEYCODE_UNKNOWN. + * To be uses for fake hardware key press. + */ + @JvmStatic fun codePointToKeyEventCode(codePoint: Int): Int = when (codePoint.toChar().uppercaseChar()) { + '/' -> KeyEvent.KEYCODE_SLASH + '\\' -> KeyEvent.KEYCODE_BACKSLASH + ';' -> KeyEvent.KEYCODE_SEMICOLON + ',' -> KeyEvent.KEYCODE_COMMA + '.' -> KeyEvent.KEYCODE_PERIOD + '\'' -> KeyEvent.KEYCODE_APOSTROPHE + '`' -> KeyEvent.KEYCODE_GRAVE + '*' -> KeyEvent.KEYCODE_STAR + ']' -> KeyEvent.KEYCODE_RIGHT_BRACKET + '[' -> KeyEvent.KEYCODE_LEFT_BRACKET + '+' -> KeyEvent.KEYCODE_PLUS + '-' -> KeyEvent.KEYCODE_MINUS + '=' -> KeyEvent.KEYCODE_EQUALS + '\n' -> KeyEvent.KEYCODE_ENTER + '\t' -> KeyEvent.KEYCODE_TAB + '0' -> KeyEvent.KEYCODE_0 + '1' -> KeyEvent.KEYCODE_1 + '2' -> KeyEvent.KEYCODE_2 + '3' -> KeyEvent.KEYCODE_3 + '4' -> KeyEvent.KEYCODE_4 + '5' -> KeyEvent.KEYCODE_5 + '6' -> KeyEvent.KEYCODE_6 + '7' -> KeyEvent.KEYCODE_7 + '8' -> KeyEvent.KEYCODE_8 + '9' -> KeyEvent.KEYCODE_9 + 'A' -> KeyEvent.KEYCODE_A + 'B' -> KeyEvent.KEYCODE_B + 'C' -> KeyEvent.KEYCODE_C + 'D' -> KeyEvent.KEYCODE_D + 'E' -> KeyEvent.KEYCODE_E + 'F' -> KeyEvent.KEYCODE_F + 'G' -> KeyEvent.KEYCODE_G + 'H' -> KeyEvent.KEYCODE_H + 'I' -> KeyEvent.KEYCODE_I + 'J' -> KeyEvent.KEYCODE_J + 'K' -> KeyEvent.KEYCODE_K + 'L' -> KeyEvent.KEYCODE_L + 'M' -> KeyEvent.KEYCODE_M + 'N' -> KeyEvent.KEYCODE_N + 'O' -> KeyEvent.KEYCODE_O + 'P' -> KeyEvent.KEYCODE_P + 'Q' -> KeyEvent.KEYCODE_Q + 'R' -> KeyEvent.KEYCODE_R + 'S' -> KeyEvent.KEYCODE_S + 'T' -> KeyEvent.KEYCODE_T + 'U' -> KeyEvent.KEYCODE_U + 'V' -> KeyEvent.KEYCODE_V + 'W' -> KeyEvent.KEYCODE_W + 'X' -> KeyEvent.KEYCODE_X + 'Y' -> KeyEvent.KEYCODE_Y + 'Z' -> KeyEvent.KEYCODE_Z else -> KeyEvent.KEYCODE_UNKNOWN } } diff --git a/app/src/main/java/helium314/keyboard/latin/define/ProductionFlags.kt b/app/src/main/java/helium314/keyboard/latin/define/ProductionFlags.kt index d476a0426..9aeb7cc3d 100644 --- a/app/src/main/java/helium314/keyboard/latin/define/ProductionFlags.kt +++ b/app/src/main/java/helium314/keyboard/latin/define/ProductionFlags.kt @@ -7,15 +7,11 @@ package helium314.keyboard.latin.define object ProductionFlags { - const val IS_HARDWARE_KEYBOARD_SUPPORTED = false - // todo: make it work - // was set to true in hangul branch (and there is the hangul hardware event decoder in latinIme) - // but disabled again because this breaks ctrl+c / ctrl+v, and most likely other things - // so it looks like the HardwareKeyboardEventDecoder needs some work before it's ready + const val IS_HARDWARE_KEYBOARD_SUPPORTED = true /** * Include all suggestions from all dictionaries in * [helium314.keyboard.latin.SuggestedWords.mRawSuggestions]. */ const val INCLUDE_RAW_SUGGESTIONS = false -} \ No newline at end of file +} diff --git a/app/src/main/java/helium314/keyboard/latin/inputlogic/InputLogic.java b/app/src/main/java/helium314/keyboard/latin/inputlogic/InputLogic.java index b1f8b5bca..3aeb602d9 100644 --- a/app/src/main/java/helium314/keyboard/latin/inputlogic/InputLogic.java +++ b/app/src/main/java/helium314/keyboard/latin/inputlogic/InputLogic.java @@ -643,7 +643,8 @@ public final class InputLogic { */ private void handleFunctionalEvent(final Event event, final InputTransaction inputTransaction, final String currentKeyboardScript, final LatinIME.UIHandler handler) { - switch (event.getMKeyCode()) { + final int keyCode = event.getMKeyCode(); + switch (keyCode) { case KeyCode.DELETE: handleBackspaceEvent(event, inputTransaction, currentKeyboardScript); // Backspace is a functional key, but it affects the contents of the editor. @@ -686,7 +687,7 @@ public final class InputLogic { case KeyCode.SHIFT_ENTER: // todo: try using sendDownUpKeyEventWithMetaState() and remove the key code maybe final Event tmpEvent = Event.createSoftwareKeypressEvent(Constants.CODE_ENTER, - event.getMKeyCode(), 0, event.getMX(), event.getMY(), event.isKeyRepeat()); + keyCode, 0, event.getMX(), event.getMY(), event.isKeyRepeat()); handleNonSpecialCharacterEvent(tmpEvent, inputTransaction, handler); // Shift + Enter is treated as a functional key but it results in adding a new // line, so that does affect the contents of the editor. @@ -777,23 +778,19 @@ public final class InputLogic { case KeyCode.CAPS_LOCK, KeyCode.EMOJI, KeyCode.TOGGLE_ONE_HANDED_MODE, KeyCode.SWITCH_ONE_HANDED_MODE: break; default: - if (KeyCode.INSTANCE.isModifier(event.getMKeyCode())) - return; // continuation of previous switch case, but modifiers are in a separate place - if (event.getMMetaState() != 0) { - // need to convert codepoint to KeyEvent.KEYCODE_ - final int codeToConvert = event.getMKeyCode() < 0 ? event.getMKeyCode() : event.getMCodePoint(); - int keyEventCode = KeyCode.INSTANCE.toKeyEventCode(codeToConvert); - if (keyEventCode != KeyEvent.KEYCODE_UNKNOWN) - sendDownUpKeyEventWithMetaState(keyEventCode, event.getMMetaState()); - return; // never crash if user inputs sth we don't have a KeyEvent.KEYCODE for - } else if (event.getMKeyCode() < 0) { - int keyEventCode = KeyCode.INSTANCE.toKeyEventCode(event.getMKeyCode()); - if (keyEventCode != KeyEvent.KEYCODE_UNKNOWN) { - sendDownUpKeyEvent(keyEventCode); - return; - } + if (KeyCode.INSTANCE.isModifier(keyCode)) + return; // continuation of previous switch case above, but modifiers are held in a separate place + final int keyEventCode = event.getMCodePoint() <= 0 + ? KeyCode.keyCodeToKeyEventCode(keyCode) + : KeyCode.codePointToKeyEventCode(event.getMCodePoint()); + if (keyEventCode != KeyEvent.KEYCODE_UNKNOWN) { + sendDownUpKeyEventWithMetaState(keyEventCode, event.getMMetaState()); + return; } - throw new RuntimeException("Unknown key code : " + event.getMKeyCode()); + // unknown event + Log.e(TAG, "unknown event, key code: "+keyCode+", functional: "+event.isFunctionalKeyEvent()+", meta: "+event.getMMetaState()); + if (DebugFlags.DEBUG_ENABLED) + throw new RuntimeException("Unknown event"); } } From 9c97a6b9bf03da216f46c504c66f282b7c94de66 Mon Sep 17 00:00:00 2001 From: Helium314 Date: Sat, 14 Jun 2025 12:16:29 +0200 Subject: [PATCH 08/26] move some key press logic from LatinIme into KeyboardActionListener --- .../java/helium314/keyboard/event/Event.kt | 10 ++ .../keyboard/KeyboardActionListener.java | 12 +++ .../keyboard/KeyboardActionListenerImpl.kt | 71 +++++++++++++- .../latin/EmojiAltPhysicalKeyDetector.java | 2 +- .../helium314/keyboard/latin/LatinIME.java | 95 +------------------ .../keyboard/latin/common/Constants.kt | 2 + .../keyboard/latin/inputlogic/InputLogic.java | 2 +- 7 files changed, 98 insertions(+), 96 deletions(-) diff --git a/app/src/main/java/helium314/keyboard/event/Event.kt b/app/src/main/java/helium314/keyboard/event/Event.kt index 5903b0c9f..37dc1ea79 100644 --- a/app/src/main/java/helium314/keyboard/event/Event.kt +++ b/app/src/main/java/helium314/keyboard/event/Event.kt @@ -139,6 +139,16 @@ class Event private constructor( null, if (isKeyRepeat) FLAG_REPEAT else FLAG_NONE, null) } + // A helper method to split the code point and the key code. + // todo: Ultimately, they should not be squashed into the same variable, and this method should be removed. + @JvmStatic + fun createSoftwareKeypressEvent(keyCodeOrCodePoint: Int, metaState: Int, keyX: Int, keyY: Int, isKeyRepeat: Boolean) = + if (keyCodeOrCodePoint <= 0) { + createSoftwareKeypressEvent(NOT_A_CODE_POINT, keyCodeOrCodePoint, metaState, keyX, keyY, isKeyRepeat) + } else { + createSoftwareKeypressEvent(keyCodeOrCodePoint, NOT_A_KEY_CODE, metaState, keyX, keyY, isKeyRepeat) + } + fun createHardwareKeypressEvent(codePoint: Int, keyCode: Int, metaState: Int, next: Event?, isKeyRepeat: Boolean): Event { return Event(EVENT_TYPE_INPUT_KEYPRESS, null, codePoint, keyCode, metaState, Constants.EXTERNAL_KEYBOARD_COORDINATE, Constants.EXTERNAL_KEYBOARD_COORDINATE, diff --git a/app/src/main/java/helium314/keyboard/keyboard/KeyboardActionListener.java b/app/src/main/java/helium314/keyboard/keyboard/KeyboardActionListener.java index fb177e806..20fb20839 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/KeyboardActionListener.java +++ b/app/src/main/java/helium314/keyboard/keyboard/KeyboardActionListener.java @@ -6,6 +6,8 @@ package helium314.keyboard.keyboard; +import android.view.KeyEvent; + import helium314.keyboard.latin.common.Constants; import helium314.keyboard.latin.common.InputPointers; @@ -31,6 +33,12 @@ public interface KeyboardActionListener { */ void onReleaseKey(int primaryCode, boolean withSliding); + /** For handling hardware key presses. Returns whether the event was handled. */ + boolean onKeyDown(int keyCode, KeyEvent keyEvent); + + /** For handling hardware key presses. Returns whether the event was handled. */ + boolean onKeyUp(int keyCode, KeyEvent keyEvent); + /** * Send a key code to the listener. * @@ -117,6 +125,10 @@ public interface KeyboardActionListener { @Override public void onReleaseKey(int primaryCode, boolean withSliding) {} @Override + public boolean onKeyDown(int keyCode, KeyEvent keyEvent) { return false; } + @Override + public boolean onKeyUp(int keyCode, KeyEvent keyEvent) { return false; } + @Override public void onCodeInput(int primaryCode, int x, int y, boolean isKeyRepeat) {} @Override public void onTextInput(String text) {} diff --git a/app/src/main/java/helium314/keyboard/keyboard/KeyboardActionListenerImpl.kt b/app/src/main/java/helium314/keyboard/keyboard/KeyboardActionListenerImpl.kt index fc002a187..a86eaeee6 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/KeyboardActionListenerImpl.kt +++ b/app/src/main/java/helium314/keyboard/keyboard/KeyboardActionListenerImpl.kt @@ -1,16 +1,24 @@ package helium314.keyboard.keyboard import android.text.InputType +import android.util.SparseArray import android.view.KeyEvent import android.view.inputmethod.InputMethodSubtype +import helium314.keyboard.event.Event +import helium314.keyboard.event.HangulEventDecoder.decodeHardwareKeyEvent +import helium314.keyboard.event.HardwareEventDecoder +import helium314.keyboard.event.HardwareKeyboardEventDecoder import helium314.keyboard.keyboard.internal.keyboard_parser.floris.KeyCode +import helium314.keyboard.latin.EmojiAltPhysicalKeyDetector import helium314.keyboard.latin.LatinIME import helium314.keyboard.latin.RichInputMethodManager import helium314.keyboard.latin.common.Constants import helium314.keyboard.latin.common.InputPointers import helium314.keyboard.latin.common.StringUtils +import helium314.keyboard.latin.common.combiningRange import helium314.keyboard.latin.common.loopOverCodePoints import helium314.keyboard.latin.common.loopOverCodePointsBackwards +import helium314.keyboard.latin.define.ProductionFlags import helium314.keyboard.latin.inputlogic.InputLogic import helium314.keyboard.latin.settings.Settings import kotlin.math.abs @@ -19,6 +27,11 @@ import kotlin.math.min class KeyboardActionListenerImpl(private val latinIME: LatinIME, private val inputLogic: InputLogic) : KeyboardActionListener { private val connection = inputLogic.mConnection + private val emojiAltPhysicalKeyDetector by lazy { EmojiAltPhysicalKeyDetector(latinIME.resources) } + + // We expect to have only one decoder in almost all cases, hence the default capacity of 1. + // If it turns out we need several, it will get grown seamlessly. + private val hardwareEventDecoders: SparseArray = SparseArray(1) private val keyboardSwitcher = KeyboardSwitcher.getInstance() private val settings = Settings.getInstance() @@ -58,9 +71,56 @@ class KeyboardActionListenerImpl(private val latinIME: LatinIME, private val inp keyboardSwitcher.onReleaseKey(primaryCode, withSliding, latinIME.currentAutoCapsState, latinIME.currentRecapitalizeState) } + override fun onKeyUp(keyCode: Int, keyEvent: KeyEvent): Boolean { + emojiAltPhysicalKeyDetector.onKeyUp(keyEvent) + if (!ProductionFlags.IS_HARDWARE_KEYBOARD_SUPPORTED) + return false + + val keyIdentifier = keyEvent.deviceId.toLong() shl 32 + keyEvent.keyCode + return inputLogic.mCurrentlyPressedHardwareKeys.remove(keyIdentifier) + } + + override fun onKeyDown(keyCode: Int, keyEvent: KeyEvent): Boolean { + emojiAltPhysicalKeyDetector.onKeyDown(keyEvent) + if (!ProductionFlags.IS_HARDWARE_KEYBOARD_SUPPORTED) + return false + + val event: Event + if (settings.current.mLocale.language == "ko") { // todo: this does not appear to be the right place + val subtype = keyboardSwitcher.keyboard?.mId?.mSubtype ?: RichInputMethodManager.getInstance().currentSubtype + event = decodeHardwareKeyEvent(subtype, keyEvent) { + getHardwareKeyEventDecoder(keyEvent.deviceId).decodeHardwareKey(keyEvent) + } + } else { + event = getHardwareKeyEventDecoder(keyEvent.deviceId).decodeHardwareKey(keyEvent) + } + + if (event.isHandled) { + inputLogic.onCodeInput( + settings.current, event, + keyboardSwitcher.getKeyboardShiftMode(), // TODO: this is not necessarily correct for a hardware keyboard right now + keyboardSwitcher.getCurrentKeyboardScript(), + latinIME.mHandler + ) + return true + } + return false + } + override fun onCodeInput(primaryCode: Int, x: Int, y: Int, isKeyRepeat: Boolean) { + when (primaryCode) { + KeyCode.TOGGLE_AUTOCORRECT -> return Settings.getInstance().toggleAutoCorrect() + KeyCode.TOGGLE_INCOGNITO_MODE -> return Settings.getInstance().toggleAlwaysIncognitoMode() + } val mkv = keyboardSwitcher.mainKeyboardView - latinIME.onCodeInput(primaryCode, metaState, mkv.getKeyX(x), mkv.getKeyY(y), isKeyRepeat) + + // checking if the character is a combining accent + val event = if (primaryCode in combiningRange) { // todo: should this be done later, maybe in inputLogic? + Event.createSoftwareDeadEvent(primaryCode, 0, metaState, mkv.getKeyX(x), mkv.getKeyY(y), null) + } else { + Event.createSoftwareKeypressEvent(primaryCode, metaState, mkv.getKeyX(x), mkv.getKeyY(y), isKeyRepeat) + } + latinIME.onEvent(event) } override fun onTextInput(text: String?) = latinIME.onTextInput(text) @@ -257,4 +317,13 @@ class KeyboardActionListenerImpl(private val latinIME: LatinIME, private val inp } return -min(-actualSteps, text.length) } + + private fun getHardwareKeyEventDecoder(deviceId: Int): HardwareEventDecoder { + hardwareEventDecoders.get(deviceId)?.let { return it } + + // TODO: create the decoder according to the specification + val newDecoder = HardwareKeyboardEventDecoder(deviceId) + hardwareEventDecoders.put(deviceId, newDecoder) + return newDecoder + } } diff --git a/app/src/main/java/helium314/keyboard/latin/EmojiAltPhysicalKeyDetector.java b/app/src/main/java/helium314/keyboard/latin/EmojiAltPhysicalKeyDetector.java index 79bfeeb24..9b06420ee 100644 --- a/app/src/main/java/helium314/keyboard/latin/EmojiAltPhysicalKeyDetector.java +++ b/app/src/main/java/helium314/keyboard/latin/EmojiAltPhysicalKeyDetector.java @@ -23,7 +23,7 @@ import java.util.List; /** * A class for detecting Emoji-Alt physical key. */ -final class EmojiAltPhysicalKeyDetector { +public final class EmojiAltPhysicalKeyDetector { private static final String TAG = "EmojiAltPhysKeyDetector"; private static final boolean DEBUG = false; diff --git a/app/src/main/java/helium314/keyboard/latin/LatinIME.java b/app/src/main/java/helium314/keyboard/latin/LatinIME.java index a7701684d..1ebcba6b8 100644 --- a/app/src/main/java/helium314/keyboard/latin/LatinIME.java +++ b/app/src/main/java/helium314/keyboard/latin/LatinIME.java @@ -26,7 +26,6 @@ import android.os.Process; import android.text.InputType; import android.util.PrintWriterPrinter; import android.util.Printer; -import android.util.SparseArray; import android.view.Gravity; import android.view.KeyEvent; import android.view.View; @@ -51,9 +50,6 @@ import helium314.keyboard.keyboard.internal.keyboard_parser.floris.KeyCode; import helium314.keyboard.latin.common.InsetsOutlineProvider; import helium314.keyboard.dictionarypack.DictionaryPackConstants; import helium314.keyboard.event.Event; -import helium314.keyboard.event.HangulEventDecoder; -import helium314.keyboard.event.HardwareEventDecoder; -import helium314.keyboard.event.HardwareKeyboardEventDecoder; import helium314.keyboard.event.InputTransaction; import helium314.keyboard.keyboard.Keyboard; import helium314.keyboard.keyboard.KeyboardId; @@ -68,7 +64,6 @@ import helium314.keyboard.latin.common.InputPointers; import helium314.keyboard.latin.common.LocaleUtils; import helium314.keyboard.latin.common.ViewOutlineProviderUtilsKt; import helium314.keyboard.latin.define.DebugFlags; -import helium314.keyboard.latin.define.ProductionFlags; import helium314.keyboard.latin.inputlogic.InputLogic; import helium314.keyboard.latin.personalization.PersonalizationHelper; import helium314.keyboard.latin.settings.Settings; @@ -134,9 +129,6 @@ public class LatinIME extends InputMethodService implements private final DictionaryFacilitator mDictionaryFacilitator = DictionaryFacilitatorProvider.getDictionaryFacilitator(false); final InputLogic mInputLogic = new InputLogic(this, this, mDictionaryFacilitator); - // We expect to have only one decoder in almost all cases, hence the default capacity of 1. - // If it turns out we need several, it will get grown seamlessly. - final SparseArray mHardwareEventDecoders = new SparseArray<>(1); // TODO: Move these {@link View}s to {@link KeyboardSwitcher}. private View mInputView; @@ -146,7 +138,6 @@ public class LatinIME extends InputMethodService implements private RichInputMethodManager mRichImm; final KeyboardSwitcher mKeyboardSwitcher; private final SubtypeState mSubtypeState = new SubtypeState(); - private EmojiAltPhysicalKeyDetector mEmojiAltPhysicalKeyDetector; private final StatsUtilsManager mStatsUtilsManager; // Working variable for {@link #startShowingInputView()} and // {@link #onEvaluateInputViewShown()}. @@ -1540,25 +1531,6 @@ public class LatinIME extends InputMethodService implements mKeyboardActionListener.onCodeInput(codePoint, x, y, isKeyRepeat); } - // called by KeyboardActionListener - public void onCodeInput(final int codePoint, final int metaState, final int x, final int y, final boolean isKeyRepeat) { - if (codePoint < 0) { - switch (codePoint) { - case KeyCode.TOGGLE_AUTOCORRECT -> {mSettings.toggleAutoCorrect(); return; } - case KeyCode.TOGGLE_INCOGNITO_MODE -> {mSettings.toggleAlwaysIncognitoMode(); return; } - } - } - final Event event; - // checking if the character is a combining accent - if (0x300 <= codePoint && codePoint <= 0x35b) { - event = Event.createSoftwareDeadEvent(codePoint, 0, metaState, x, y, null); - } else { - event = createSoftwareKeypressEvent(codePoint, metaState, x, y, isKeyRepeat); - } - - onEvent(event); - } - // This method is public for testability of LatinIME, but also in the future it should // completely replace #onCodeInput. public void onEvent(@NonNull final Event event) { @@ -1573,24 +1545,6 @@ public class LatinIME extends InputMethodService implements mKeyboardSwitcher.onEvent(event, getCurrentAutoCapsState(), getCurrentRecapitalizeState()); } - // A helper method to split the code point and the key code. Ultimately, they should not be - // squashed into the same variable, and this method should be removed. - // public for testing, as we don't want to copy the same logic into test code - @NonNull - public static Event createSoftwareKeypressEvent(final int keyCodeOrCodePoint, final int metaState, - final int keyX, final int keyY, final boolean isKeyRepeat) { - final int keyCode; - final int codePoint; - if (keyCodeOrCodePoint <= 0) { - keyCode = keyCodeOrCodePoint; - codePoint = Event.NOT_A_CODE_POINT; - } else { - keyCode = Event.NOT_A_KEY_CODE; - codePoint = keyCodeOrCodePoint; - } - return Event.createSoftwareKeypressEvent(codePoint, keyCode, metaState, keyX, keyY, isKeyRepeat); - } - public void onTextInput(final String rawText) { // TODO: have the keyboard pass the correct key code when we need it. final Event event = Event.createSoftwareTextEvent(rawText, KeyCode.MULTIPLE_CODE_POINTS); @@ -1830,63 +1784,18 @@ public class LatinIME extends InputMethodService implements feedbackManager.performAudioFeedback(code); } - private HardwareEventDecoder getHardwareKeyEventDecoder(final int deviceId) { - final HardwareEventDecoder decoder = mHardwareEventDecoders.get(deviceId); - if (null != decoder) return decoder; - // TODO: create the decoder according to the specification - final HardwareEventDecoder newDecoder = new HardwareKeyboardEventDecoder(deviceId); - mHardwareEventDecoders.put(deviceId, newDecoder); - return newDecoder; - } - // Hooks for hardware keyboard @Override public boolean onKeyDown(final int keyCode, final KeyEvent keyEvent) { - if (mEmojiAltPhysicalKeyDetector == null) { - mEmojiAltPhysicalKeyDetector = new EmojiAltPhysicalKeyDetector( - getApplicationContext().getResources()); - } - mEmojiAltPhysicalKeyDetector.onKeyDown(keyEvent); - if (!ProductionFlags.IS_HARDWARE_KEYBOARD_SUPPORTED) { - return super.onKeyDown(keyCode, keyEvent); - } - final Event event; - if (mRichImm.getCurrentSubtypeLocale().getLanguage().equals("ko")) { - final RichInputMethodSubtype subtype = mKeyboardSwitcher.getKeyboard() == null - ? mRichImm.getCurrentSubtype() - : mKeyboardSwitcher.getKeyboard().mId.mSubtype; - event = HangulEventDecoder.decodeHardwareKeyEvent(subtype, keyEvent, - () -> getHardwareKeyEventDecoder(keyEvent.getDeviceId()).decodeHardwareKey(keyEvent)); - } else { - event = getHardwareKeyEventDecoder(keyEvent.getDeviceId()).decodeHardwareKey(keyEvent); - } - // If the event is not handled by LatinIME, we just pass it to the parent implementation. - // If it's handled, we return true because we did handle it. - if (event.isHandled()) { - mInputLogic.onCodeInput(mSettings.getCurrent(), event, - mKeyboardSwitcher.getKeyboardShiftMode(), - // TODO: this is not necessarily correct for a hardware keyboard right now - mKeyboardSwitcher.getCurrentKeyboardScript(), - mHandler); + if (mKeyboardActionListener.onKeyDown(keyCode, keyEvent)) return true; - } return super.onKeyDown(keyCode, keyEvent); } @Override public boolean onKeyUp(final int keyCode, final KeyEvent keyEvent) { - if (mEmojiAltPhysicalKeyDetector == null) { - mEmojiAltPhysicalKeyDetector = new EmojiAltPhysicalKeyDetector( - getApplicationContext().getResources()); - } - mEmojiAltPhysicalKeyDetector.onKeyUp(keyEvent); - if (!ProductionFlags.IS_HARDWARE_KEYBOARD_SUPPORTED) { - return super.onKeyUp(keyCode, keyEvent); - } - final long keyIdentifier = (long) keyEvent.getDeviceId() << 32 + keyEvent.getKeyCode(); - if (mInputLogic.mCurrentlyPressedHardwareKeys.remove(keyIdentifier)) { + if (mKeyboardActionListener.onKeyUp(keyCode, keyEvent)) return true; - } return super.onKeyUp(keyCode, keyEvent); } diff --git a/app/src/main/java/helium314/keyboard/latin/common/Constants.kt b/app/src/main/java/helium314/keyboard/latin/common/Constants.kt index 17b36d1da..43b9e66dd 100644 --- a/app/src/main/java/helium314/keyboard/latin/common/Constants.kt +++ b/app/src/main/java/helium314/keyboard/latin/common/Constants.kt @@ -12,3 +12,5 @@ object Links { const val CUSTOM_LAYOUTS = "$GITHUB/discussions/categories/custom-layout" const val CUSTOM_COLORS = "$GITHUB/discussions/categories/custom-colors" } + +val combiningRange = 0x300..0x35b diff --git a/app/src/main/java/helium314/keyboard/latin/inputlogic/InputLogic.java b/app/src/main/java/helium314/keyboard/latin/inputlogic/InputLogic.java index 3aeb602d9..d34c276aa 100644 --- a/app/src/main/java/helium314/keyboard/latin/inputlogic/InputLogic.java +++ b/app/src/main/java/helium314/keyboard/latin/inputlogic/InputLogic.java @@ -718,7 +718,7 @@ public final class InputLogic { if (mConnection.hasSelection()) { mConnection.copyText(true); // fake delete keypress to remove the text - final Event backspaceEvent = LatinIME.createSoftwareKeypressEvent(KeyCode.DELETE, 0, + final Event backspaceEvent = Event.createSoftwareKeypressEvent(KeyCode.DELETE, 0, event.getMX(), event.getMY(), event.isKeyRepeat()); handleBackspaceEvent(backspaceEvent, inputTransaction, currentKeyboardScript); inputTransaction.setDidAffectContents(); From e430d13c4a256ef380b2fe7057ed9bf472d9a953 Mon Sep 17 00:00:00 2001 From: Helium314 Date: Sat, 14 Jun 2025 12:53:46 +0200 Subject: [PATCH 09/26] improvements to hardware keyboard handling --- .../event/HardwareKeyboardEventDecoder.kt | 18 +++++++++++++++++- .../keyboard/latin/inputlogic/InputLogic.java | 9 +++++---- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/helium314/keyboard/event/HardwareKeyboardEventDecoder.kt b/app/src/main/java/helium314/keyboard/event/HardwareKeyboardEventDecoder.kt index 45dcc27b4..0578a6ffb 100644 --- a/app/src/main/java/helium314/keyboard/event/HardwareKeyboardEventDecoder.kt +++ b/app/src/main/java/helium314/keyboard/event/HardwareKeyboardEventDecoder.kt @@ -10,6 +10,8 @@ import android.view.KeyCharacterMap import android.view.KeyEvent import helium314.keyboard.keyboard.internal.keyboard_parser.floris.KeyCode import helium314.keyboard.latin.common.Constants +import helium314.keyboard.latin.utils.Log +import helium314.keyboard.latin.utils.getCustomKeyCode /** * A hardware event decoder for a hardware qwerty-ish keyboard. @@ -24,7 +26,8 @@ class HardwareKeyboardEventDecoder(val mDeviceId: Int) : HardwareEventDecoder { // KeyEvent#getUnicodeChar() does not exactly returns a unicode char, but rather a value // that includes both the unicode char in the lower 21 bits and flags in the upper bits, // hence the name "codePointAndFlags". {@see KeyEvent#getUnicodeChar()} for more info. - val codePointAndFlags = keyEvent.unicodeChar + val codePointAndFlags = keyEvent.unicodeChar.takeIf { it != 0 } + ?: Event.NOT_A_CODE_POINT // KeyEvent has 0 if no codePoint, but that's actually valid so we convert it to -1 // The keyCode is the abstraction used by the KeyEvent to represent different keys that // do not necessarily map to a unicode character. This represents a physical key, like // the key for 'A' or Space, but also Backspace or Ctrl or Caps Lock. @@ -48,8 +51,21 @@ class HardwareKeyboardEventDecoder(val mDeviceId: Int) : HardwareEventDecoder { } else Event.createHardwareKeypressEvent(codePointAndFlags, keyCode, metaState, null, isKeyRepeat) // If not Enter, then this is just a regular keypress event for a normal character // that can be committed right away, taking into account the current state. + } else if (isDpadDirection(keyCode)) { + Event.createHardwareKeypressEvent(codePointAndFlags, keyCode, metaState, null, isKeyRepeat) +// } else if (KeyEvent.isModifierKey(keyCode)) { +// todo: we could synchronize meta state across HW and SW keyboard, but that's more work for little benefit (especially with shift & caps lock) } else { Event.notHandledEvent } } + + companion object { + private fun isDpadDirection(keyCode: Int) = when (keyCode) { + KeyEvent.KEYCODE_DPAD_UP, KeyEvent.KEYCODE_DPAD_DOWN, KeyEvent.KEYCODE_DPAD_LEFT, KeyEvent.KEYCODE_DPAD_RIGHT, + KeyEvent.KEYCODE_DPAD_DOWN_LEFT, KeyEvent.KEYCODE_DPAD_DOWN_RIGHT, KeyEvent.KEYCODE_DPAD_UP_RIGHT, + KeyEvent.KEYCODE_DPAD_UP_LEFT -> true + else -> false + } + } } diff --git a/app/src/main/java/helium314/keyboard/latin/inputlogic/InputLogic.java b/app/src/main/java/helium314/keyboard/latin/inputlogic/InputLogic.java index d34c276aa..63004024a 100644 --- a/app/src/main/java/helium314/keyboard/latin/inputlogic/InputLogic.java +++ b/app/src/main/java/helium314/keyboard/latin/inputlogic/InputLogic.java @@ -780,15 +780,16 @@ public final class InputLogic { default: if (KeyCode.INSTANCE.isModifier(keyCode)) return; // continuation of previous switch case above, but modifiers are held in a separate place - final int keyEventCode = event.getMCodePoint() <= 0 - ? KeyCode.keyCodeToKeyEventCode(keyCode) - : KeyCode.codePointToKeyEventCode(event.getMCodePoint()); + final int keyEventCode = keyCode > 0 + ? keyCode + : event.getMCodePoint() >= 0 ? KeyCode.codePointToKeyEventCode(event.getMCodePoint()) + : KeyCode.keyCodeToKeyEventCode(keyCode); if (keyEventCode != KeyEvent.KEYCODE_UNKNOWN) { sendDownUpKeyEventWithMetaState(keyEventCode, event.getMMetaState()); return; } // unknown event - Log.e(TAG, "unknown event, key code: "+keyCode+", functional: "+event.isFunctionalKeyEvent()+", meta: "+event.getMMetaState()); + Log.e(TAG, "unknown event, key code: "+keyCode+", meta: "+event.getMMetaState()); if (DebugFlags.DEBUG_ENABLED) throw new RuntimeException("Unknown event"); } From ef3191a2eb8f9e034f71a8a723db2f42ae1dedad Mon Sep 17 00:00:00 2001 From: Helium314 Date: Sun, 15 Jun 2025 17:41:05 +0200 Subject: [PATCH 10/26] prepare for selecting text with cursor movement keys when shift is enabled manually not enabled, as it's not working reliably --- .../event/HardwareKeyboardEventDecoder.kt | 2 - .../keyboard/KeyboardActionListenerImpl.kt | 10 ++++- .../keyboard/keyboard/KeyboardId.java | 5 +++ .../helium314/keyboard/latin/LatinIME.java | 8 +++- .../keyboard/latin/inputlogic/InputLogic.java | 39 ++++++++++++------- 5 files changed, 44 insertions(+), 20 deletions(-) diff --git a/app/src/main/java/helium314/keyboard/event/HardwareKeyboardEventDecoder.kt b/app/src/main/java/helium314/keyboard/event/HardwareKeyboardEventDecoder.kt index 0578a6ffb..a8d2d7f32 100644 --- a/app/src/main/java/helium314/keyboard/event/HardwareKeyboardEventDecoder.kt +++ b/app/src/main/java/helium314/keyboard/event/HardwareKeyboardEventDecoder.kt @@ -10,8 +10,6 @@ import android.view.KeyCharacterMap import android.view.KeyEvent import helium314.keyboard.keyboard.internal.keyboard_parser.floris.KeyCode import helium314.keyboard.latin.common.Constants -import helium314.keyboard.latin.utils.Log -import helium314.keyboard.latin.utils.getCustomKeyCode /** * A hardware event decoder for a hardware qwerty-ish keyboard. diff --git a/app/src/main/java/helium314/keyboard/keyboard/KeyboardActionListenerImpl.kt b/app/src/main/java/helium314/keyboard/keyboard/KeyboardActionListenerImpl.kt index a86eaeee6..f75e2825e 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/KeyboardActionListenerImpl.kt +++ b/app/src/main/java/helium314/keyboard/keyboard/KeyboardActionListenerImpl.kt @@ -5,7 +5,7 @@ import android.util.SparseArray import android.view.KeyEvent import android.view.inputmethod.InputMethodSubtype import helium314.keyboard.event.Event -import helium314.keyboard.event.HangulEventDecoder.decodeHardwareKeyEvent +import helium314.keyboard.event.HangulEventDecoder import helium314.keyboard.event.HardwareEventDecoder import helium314.keyboard.event.HardwareKeyboardEventDecoder import helium314.keyboard.keyboard.internal.keyboard_parser.floris.KeyCode @@ -88,7 +88,7 @@ class KeyboardActionListenerImpl(private val latinIME: LatinIME, private val inp val event: Event if (settings.current.mLocale.language == "ko") { // todo: this does not appear to be the right place val subtype = keyboardSwitcher.keyboard?.mId?.mSubtype ?: RichInputMethodManager.getInstance().currentSubtype - event = decodeHardwareKeyEvent(subtype, keyEvent) { + event = HangulEventDecoder.decodeHardwareKeyEvent(subtype, keyEvent) { getHardwareKeyEventDecoder(keyEvent.deviceId).decodeHardwareKey(keyEvent) } } else { @@ -118,6 +118,12 @@ class KeyboardActionListenerImpl(private val latinIME: LatinIME, private val inp val event = if (primaryCode in combiningRange) { // todo: should this be done later, maybe in inputLogic? Event.createSoftwareDeadEvent(primaryCode, 0, metaState, mkv.getKeyX(x), mkv.getKeyY(y), null) } else { + // todo: + // setting meta shift should only be done for arrow and similar cursor movement keys + // should only be enabled once it works more reliably (currently depends on app for some reason) +// if (mkv.keyboard?.mId?.isAlphabetShiftedManually == true) +// Event.createSoftwareKeypressEvent(primaryCode, metaState or KeyEvent.META_SHIFT_ON, mkv.getKeyX(x), mkv.getKeyY(y), isKeyRepeat) +// else Event.createSoftwareKeypressEvent(primaryCode, metaState, mkv.getKeyX(x), mkv.getKeyY(y), isKeyRepeat) Event.createSoftwareKeypressEvent(primaryCode, metaState, mkv.getKeyX(x), mkv.getKeyY(y), isKeyRepeat) } latinIME.onEvent(event) diff --git a/app/src/main/java/helium314/keyboard/keyboard/KeyboardId.java b/app/src/main/java/helium314/keyboard/keyboard/KeyboardId.java index ac39680a8..62523f488 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/KeyboardId.java +++ b/app/src/main/java/helium314/keyboard/keyboard/KeyboardId.java @@ -184,6 +184,11 @@ public final class KeyboardId { || mElementId == ELEMENT_ALPHABET_AUTOMATIC_SHIFTED || mElementId == ELEMENT_ALPHABET_MANUAL_SHIFTED; } + public boolean isAlphabetShiftedManually() { + return mElementId == ELEMENT_ALPHABET_SHIFT_LOCKED || mElementId == ELEMENT_ALPHABET_SHIFT_LOCK_SHIFTED + || mElementId == ELEMENT_ALPHABET_MANUAL_SHIFTED; + } + public boolean isNumberLayout() { return mElementId == ELEMENT_NUMBER || mElementId == ELEMENT_NUMPAD || mElementId == ELEMENT_PHONE || mElementId == ELEMENT_PHONE_SYMBOLS; diff --git a/app/src/main/java/helium314/keyboard/latin/LatinIME.java b/app/src/main/java/helium314/keyboard/latin/LatinIME.java index 1ebcba6b8..917aff58d 100644 --- a/app/src/main/java/helium314/keyboard/latin/LatinIME.java +++ b/app/src/main/java/helium314/keyboard/latin/LatinIME.java @@ -1164,8 +1164,12 @@ public class LatinIME extends InputMethodService implements if (isInputViewShown() && mInputLogic.onUpdateSelection(oldSelStart, oldSelEnd, newSelStart, newSelEnd, composingSpanStart, composingSpanEnd, settingsValues)) { - mKeyboardSwitcher.requestUpdatingShiftState(getCurrentAutoCapsState(), - getCurrentRecapitalizeState()); + // we don't want to update a manually set shift state if selection changed towards one side + // because this may end the manual shift, which is unwanted in case of shift + arrow keys for changing selection + // todo: this is not fully implemented yet, and maybe should be behind a setting + if (mKeyboardSwitcher.getKeyboard() != null && mKeyboardSwitcher.getKeyboard().mId.isAlphabetShiftedManually() + && !((oldSelEnd == newSelEnd && oldSelStart != newSelStart) || (oldSelEnd != newSelEnd && oldSelStart == newSelStart))) + mKeyboardSwitcher.requestUpdatingShiftState(getCurrentAutoCapsState(), getCurrentRecapitalizeState()); } } diff --git a/app/src/main/java/helium314/keyboard/latin/inputlogic/InputLogic.java b/app/src/main/java/helium314/keyboard/latin/inputlogic/InputLogic.java index 63004024a..6fcd24a68 100644 --- a/app/src/main/java/helium314/keyboard/latin/inputlogic/InputLogic.java +++ b/app/src/main/java/helium314/keyboard/latin/inputlogic/InputLogic.java @@ -89,6 +89,7 @@ public final class InputLogic { private int mDeleteCount; private long mLastKeyTime; + // todo: this is not used, so either remove it or do something with it public final TreeSet mCurrentlyPressedHardwareKeys = new TreeSet<>(); // Keeps track of most recently inserted text (multi-character key) for reverting @@ -399,7 +400,11 @@ public final class InputLogic { // Stop the last recapitalization, if started. mRecapitalizeStatus.stop(); mWordBeingCorrectedByCursor = null; - return true; + + // we do not return true if + final boolean oneSidedSelectionMove = hasOrHadSelection + && ((oldSelEnd == newSelEnd && oldSelStart != newSelStart) || (oldSelEnd != newSelEnd && oldSelStart == newSelStart)); + return !oneSidedSelectionMove; } public boolean moveCursorByAndReturnIfInsideComposingWord(int distance) { @@ -725,30 +730,36 @@ public final class InputLogic { } break; case KeyCode.WORD_LEFT: - sendDownUpKeyEventWithMetaState(ScriptUtils.isScriptRtl(currentKeyboardScript)? - KeyEvent.KEYCODE_DPAD_RIGHT : KeyEvent.KEYCODE_DPAD_LEFT, KeyEvent.META_CTRL_ON); + sendDownUpKeyEventWithMetaState( + ScriptUtils.isScriptRtl(currentKeyboardScript) ? KeyEvent.KEYCODE_DPAD_RIGHT : KeyEvent.KEYCODE_DPAD_LEFT, + KeyEvent.META_CTRL_ON | event.getMMetaState()); break; case KeyCode.WORD_RIGHT: - sendDownUpKeyEventWithMetaState(ScriptUtils.isScriptRtl(currentKeyboardScript)? - KeyEvent.KEYCODE_DPAD_LEFT : KeyEvent.KEYCODE_DPAD_RIGHT, KeyEvent.META_CTRL_ON); + sendDownUpKeyEventWithMetaState( + ScriptUtils.isScriptRtl(currentKeyboardScript) ? KeyEvent.KEYCODE_DPAD_LEFT : KeyEvent.KEYCODE_DPAD_RIGHT, + KeyEvent.META_CTRL_ON | event.getMMetaState()); break; case KeyCode.MOVE_START_OF_PAGE: - final int selectionEnd = mConnection.getExpectedSelectionEnd(); - sendDownUpKeyEventWithMetaState(KeyEvent.KEYCODE_MOVE_HOME, KeyEvent.META_CTRL_ON); - if (mConnection.getExpectedSelectionStart() > 0 && mConnection.getExpectedSelectionEnd() == selectionEnd) { - // unchanged, and we're not at the top -> try a different method (necessary for compose fields) - mConnection.setSelection(0, 0); + final int selectionEnd1 = mConnection.getExpectedSelectionEnd(); + final int selectionStart1 = mConnection.getExpectedSelectionStart(); + sendDownUpKeyEventWithMetaState(KeyEvent.KEYCODE_MOVE_HOME, KeyEvent.META_CTRL_ON | event.getMMetaState()); + if (mConnection.getExpectedSelectionStart() == selectionStart1 && mConnection.getExpectedSelectionEnd() == selectionEnd1) { + // unchanged -> try a different method (necessary for compose fields) + final int newEnd = (event.getMMetaState() & KeyEvent.META_SHIFT_MASK) != 0 ? selectionEnd1 : 0; + mConnection.setSelection(0, newEnd); } break; case KeyCode.MOVE_END_OF_PAGE: - final int selectionStart = mConnection.getExpectedSelectionEnd(); - sendDownUpKeyEventWithMetaState(KeyEvent.KEYCODE_MOVE_END, KeyEvent.META_CTRL_ON); - if (mConnection.getExpectedSelectionStart() == selectionStart) { + final int selectionStart2 = mConnection.getExpectedSelectionStart(); + final int selectionEnd2 = mConnection.getExpectedSelectionEnd(); + sendDownUpKeyEventWithMetaState(KeyEvent.KEYCODE_MOVE_END, KeyEvent.META_CTRL_ON | event.getMMetaState()); + if (mConnection.getExpectedSelectionStart() == selectionStart2 && mConnection.getExpectedSelectionEnd() == selectionEnd2) { // unchanged, try fallback e.g. for compose fields that don't care about ctrl + end // we just move to a very large index, and hope the field is prepared to deal with this // getting the actual length of the text for setting the correct position can be tricky for some apps... try { - mConnection.setSelection(Integer.MAX_VALUE, Integer.MAX_VALUE); + final int newStart = (event.getMMetaState() & KeyEvent.META_SHIFT_MASK) != 0 ? selectionStart2 : Integer.MAX_VALUE; + mConnection.setSelection(newStart, Integer.MAX_VALUE); } catch (Exception e) { // better catch potential errors and just do nothing in this case Log.i(TAG, "error when trying to move cursor to last position: " + e); From 24a2eddc1f5ae280f9057817aaa8e9e76c6780f2 Mon Sep 17 00:00:00 2001 From: Helium314 Date: Sun, 15 Jun 2025 17:43:06 +0200 Subject: [PATCH 11/26] avoid StringIndexOutOfBoundsException when initializing stringbuilder from other stringbuilder --- .../main/java/helium314/keyboard/latin/RichInputConnection.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/helium314/keyboard/latin/RichInputConnection.java b/app/src/main/java/helium314/keyboard/latin/RichInputConnection.java index 7d34df49b..f7e8bcc03 100644 --- a/app/src/main/java/helium314/keyboard/latin/RichInputConnection.java +++ b/app/src/main/java/helium314/keyboard/latin/RichInputConnection.java @@ -439,7 +439,7 @@ public final class RichInputConnection implements PrivateCommandPerformer { // test for this explicitly) if (INVALID_CURSOR_POSITION != mExpectedSelStart && (cachedLength >= n || cachedLength >= mExpectedSelStart)) { - final StringBuilder s = new StringBuilder(mCommittedTextBeforeComposingText); + final StringBuilder s = new StringBuilder(mCommittedTextBeforeComposingText.toString()); // We call #toString() here to create a temporary object. // In some situations, this method is called on a worker thread, and it's possible // the main thread touches the contents of mComposingText while this worker thread From 2dc838798dd840ec679a2676856964cbe7c1e4e6 Mon Sep 17 00:00:00 2001 From: Helium314 Date: Sun, 15 Jun 2025 19:49:32 +0200 Subject: [PATCH 12/26] use hasLabels for TLD popups and add documentation --- .../internal/keyboard_parser/LocaleKeyboardInfos.kt | 2 +- layouts.md | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/LocaleKeyboardInfos.kt b/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/LocaleKeyboardInfos.kt index a67fca6bd..274aeb0c4 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/LocaleKeyboardInfos.kt +++ b/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/LocaleKeyboardInfos.kt @@ -45,7 +45,7 @@ class LocaleKeyboardInfos(dataStream: InputStream?, locale: Locale) { "mns" -> Key.LABEL_FLAGS_FOLLOW_KEY_LETTER_RATIO else -> 0 } - val tlds = mutableListOf() + val tlds = mutableListOf(Key.POPUP_KEYS_HAS_LABELS) init { readStream(dataStream, false, true) diff --git a/layouts.md b/layouts.md index d03b20f98..59b092a9a 100644 --- a/layouts.md +++ b/layouts.md @@ -73,7 +73,7 @@ If the layout has exactly 2 keys in the bottom row, these keys will replace comm * `0.1` for phones * `0.09` for tablets * If the sum of widths in a row is greater than 1, keys are rescaled to fit on the screen -* `labelFlags`: allows specific effects, see [here](app/src/main/res/values/attrs.xml#L251-L287) in the section _keyLabelFlags_ for names and numeric values +* `labelFlags`: allows specific effects, see [here](app/src/main/res/values/attrs.xml#L250-L282) in the section _keyLabelFlags_ for names and numeric values * Since json does not support hexadecimal-values, you have to use the decimal values in the comments in the same line. * In case you want to apply multiple flags, you will need to combine them using [bitwise OR](https://en.wikipedia.org/wiki/Bitwise_operation#OR). In most cases this means you can just add the individual values, only exceptions are `fontDefault`, `followKeyLabelRatio`, `followKeyHintLabelRatio`, and `autoScale`. @@ -106,6 +106,12 @@ Usually the label is what is displayed on the key. However, there are some speci You can also specify special key codes like `a|!code/key_action_previous` or `abc|!code/-10043`, but it's cleaner to use a json layout and specify the code explicitly. Note that when specifying a code in the label, and a code in a json layout, the code in the label will be ignored. * It's also possible to specify an icon, like `!icon/previous_key|!code/key_action_previous`. * You can find available icon names in [KeyboardIconsSet](/app/src/main/java/helium314/keyboard/keyboard/internal/KeyboardIconsSet.kt). You can also use toolbar key icons using the uppercase name of the [toolbar key](/app/src/main/java/helium314/keyboard/latin/utils/ToolbarUtils.kt#L109), e.g. `!icon/redo` +* There are some further special labels to be used in popup keys (i.e. one of the popup keys should have the label) + * `!noPanelAutoPopupKey!`: no popups are shown, a long press will result in the first normal popup of the key being selected + * `!needsDividers!`: dividers are shown between popup keys + * `!hasLabels!`: reduces text size in popup keys for nicer display of labels instead of letters + * `!autoColumnOrder!`: use with a number, e.g. _!autoColumnOrder!4_ will result in 4 popup columns + * `!fixedColumnOrder!`: use with a number, e.g. _!fixedColumnOrder!4_ will result in 4 popup columns. Keys will not be re-ordered if the result is a single line. ## Adding new layouts / languages * You need a layout file in one of the formats above, and add it to [layouts](app/src/main/assets/layouts) From 9549389be7bdae6a62683db49d1db3fe4564c363 Mon Sep 17 00:00:00 2001 From: Helium314 Date: Sun, 15 Jun 2025 21:09:53 +0200 Subject: [PATCH 13/26] use different method for hiding keyboard fixes GH-1719 --- .../java/helium314/keyboard/latin/inputlogic/InputLogic.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/helium314/keyboard/latin/inputlogic/InputLogic.java b/app/src/main/java/helium314/keyboard/latin/inputlogic/InputLogic.java index 6fcd24a68..058ad3792 100644 --- a/app/src/main/java/helium314/keyboard/latin/inputlogic/InputLogic.java +++ b/app/src/main/java/helium314/keyboard/latin/inputlogic/InputLogic.java @@ -779,7 +779,7 @@ public final class InputLogic { mLatinIME.onTextInput(TimestampKt.getTimestamp(mLatinIME)); break; case KeyCode.IME_HIDE_UI: - mLatinIME.hideWindow(); + mLatinIME.requestHideSelf(0); break; case KeyCode.VOICE_INPUT: // switching to shortcut IME, shift state, keyboard,... is handled by LatinIME, From d356f9f54bbc78c06110ec47b594482b14a9e0ff Mon Sep 17 00:00:00 2001 From: lurebat <154669821+lurebat@users.noreply.github.com> Date: Mon, 16 Jun 2025 19:55:15 +0300 Subject: [PATCH 14/26] Add broadcast intent keys (#1675) This commit introduces three new keycodes: SEND_INTENT_ONE, SEND_INTENT_TWO, and SEND_INTENT_THREE. When these keys are pressed, a broadcast intent is sent with the action `helium314.keyboard.latin.ACTION_SEND_INTENT`. The intent includes an extra `EXTRA_NUMBER` (integer) indicating which of the three keys was pressed (1, 2, or 3). This functionality allows external applications to react to these specific key presses. --- .../keyboard_parser/floris/KeyCode.kt | 8 +++++- .../keyboard/latin/inputlogic/InputLogic.java | 3 +++ .../keyboard/latin/utils/IntentUtils.kt | 26 +++++++++++++++++++ 3 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 app/src/main/java/helium314/keyboard/latin/utils/IntentUtils.kt diff --git a/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/floris/KeyCode.kt b/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/floris/KeyCode.kt index cf34b4228..eccf62ebb 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/floris/KeyCode.kt +++ b/app/src/main/java/helium314/keyboard/keyboard/internal/keyboard_parser/floris/KeyCode.kt @@ -175,6 +175,12 @@ object KeyCode { const val META_LEFT = -10048 const val META_RIGHT = -10049 + + // Intents + const val SEND_INTENT_ONE = -20000 + const val SEND_INTENT_TWO = -20001 + const val SEND_INTENT_THREE = -20002 + /** to make sure a FlorisBoard code works when reading a JSON layout */ fun Int.checkAndConvertCode(): Int = if (this > 0) this else when (this) { // working @@ -190,7 +196,7 @@ object KeyCode { ACTION_NEXT, ACTION_PREVIOUS, NOT_SPECIFIED, CLIPBOARD_COPY_ALL, WORD_LEFT, WORD_RIGHT, PAGE_UP, PAGE_DOWN, META, TAB, ESCAPE, INSERT, SLEEP, MEDIA_PLAY, MEDIA_PAUSE, MEDIA_PLAY_PAUSE, MEDIA_NEXT, MEDIA_PREVIOUS, VOL_UP, VOL_DOWN, MUTE, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, BACK, - TIMESTAMP, CTRL_LEFT, CTRL_RIGHT, ALT_LEFT, ALT_RIGHT, META_LEFT, META_RIGHT + TIMESTAMP, CTRL_LEFT, CTRL_RIGHT, ALT_LEFT, ALT_RIGHT, META_LEFT, META_RIGHT, SEND_INTENT_ONE, SEND_INTENT_TWO, SEND_INTENT_THREE, -> this // conversion diff --git a/app/src/main/java/helium314/keyboard/latin/inputlogic/InputLogic.java b/app/src/main/java/helium314/keyboard/latin/inputlogic/InputLogic.java index 058ad3792..28572fa2f 100644 --- a/app/src/main/java/helium314/keyboard/latin/inputlogic/InputLogic.java +++ b/app/src/main/java/helium314/keyboard/latin/inputlogic/InputLogic.java @@ -49,6 +49,7 @@ import helium314.keyboard.latin.settings.SpacingAndPunctuations; import helium314.keyboard.latin.suggestions.SuggestionStripViewAccessor; import helium314.keyboard.latin.utils.AsyncResultHolder; import helium314.keyboard.latin.utils.InputTypeUtils; +import helium314.keyboard.latin.utils.IntentUtils; import helium314.keyboard.latin.utils.Log; import helium314.keyboard.latin.utils.RecapitalizeStatus; import helium314.keyboard.latin.utils.ScriptUtils; @@ -778,6 +779,8 @@ public final class InputLogic { case KeyCode.TIMESTAMP: mLatinIME.onTextInput(TimestampKt.getTimestamp(mLatinIME)); break; + case KeyCode.SEND_INTENT_ONE, KeyCode.SEND_INTENT_TWO, KeyCode.SEND_INTENT_THREE: + IntentUtils.handleSendIntentKey(mLatinIME, event.getMKeyCode()); case KeyCode.IME_HIDE_UI: mLatinIME.requestHideSelf(0); break; diff --git a/app/src/main/java/helium314/keyboard/latin/utils/IntentUtils.kt b/app/src/main/java/helium314/keyboard/latin/utils/IntentUtils.kt new file mode 100644 index 000000000..da1b06bd8 --- /dev/null +++ b/app/src/main/java/helium314/keyboard/latin/utils/IntentUtils.kt @@ -0,0 +1,26 @@ +package helium314.keyboard.latin.utils + +import android.content.Context +import android.content.Intent +import helium314.keyboard.keyboard.internal.keyboard_parser.floris.KeyCode +import helium314.keyboard.latin.inputlogic.InputLogic +import helium314.keyboard.latin.utils.Log.i + +object IntentUtils { + val TAG: String = InputLogic::class.java.simpleName + private val ACTION_SEND_INTENT = "helium314.keyboard.latin.ACTION_SEND_INTENT" + private val EXTRA_NUMBER = "EXTRA_NUMBER" + + @JvmStatic + fun handleSendIntentKey(context: Context, mKeyCode: Int) { + val intentNumber = (KeyCode.SEND_INTENT_ONE + 1) - mKeyCode; + + val intent: Intent = Intent(ACTION_SEND_INTENT).apply { + putExtra(EXTRA_NUMBER, intentNumber) + } + + context.sendBroadcast(intent) + i(TAG, "Sent broadcast for intent number: $intentNumber") + } +} + From e062efb3d4859acbf9deffc883e81fae4efd8ffb Mon Sep 17 00:00:00 2001 From: Eran Leshem <1707552+eranl@users.noreply.github.com> Date: Mon, 16 Jun 2025 20:14:47 +0300 Subject: [PATCH 15/26] Always default popup `VisibleOffset` to `keyboard.mVerticalGap` (#1722) --- .../keyboard/keyboard/MainKeyboardView.java | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/helium314/keyboard/keyboard/MainKeyboardView.java b/app/src/main/java/helium314/keyboard/keyboard/MainKeyboardView.java index 3a3fa3e16..76a01e8e5 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/MainKeyboardView.java +++ b/app/src/main/java/helium314/keyboard/keyboard/MainKeyboardView.java @@ -360,25 +360,21 @@ public final class MainKeyboardView extends KeyboardView implements DrawingProxy public void onKeyPressed(@NonNull final Key key, final boolean withPreview) { key.onPressed(); invalidateKey(key); - if (withPreview && !key.noKeyPreview()) { + + final Keyboard keyboard = getKeyboard(); + if (keyboard == null) { + return; + } + mKeyPreviewDrawParams.setVisibleOffset(-keyboard.mVerticalGap); + if (withPreview && !key.noKeyPreview() && mKeyPreviewDrawParams.isPopupEnabled()) { showKeyPreview(key); } } private void showKeyPreview(@NonNull final Key key) { - final Keyboard keyboard = getKeyboard(); - if (keyboard == null) { - return; - } - final KeyPreviewDrawParams previewParams = mKeyPreviewDrawParams; - if (!previewParams.isPopupEnabled()) { - previewParams.setVisibleOffset(-keyboard.mVerticalGap); - return; - } - locatePreviewPlacerView(); getLocationInWindow(mOriginCoords); - mKeyPreviewChoreographer.placeAndShowKeyPreview(key, keyboard.mIconsSet, getKeyDrawParams(), + mKeyPreviewChoreographer.placeAndShowKeyPreview(key, getKeyboard().mIconsSet, getKeyDrawParams(), KeyboardSwitcher.getInstance().getWrapperView().getWidth(), mOriginCoords, mDrawingPreviewPlacerView); } From 7b0c51185784267d7379156c273db15147177398 Mon Sep 17 00:00:00 2001 From: Helium314 Date: Mon, 16 Jun 2025 19:43:32 +0200 Subject: [PATCH 16/26] add logging when hiding window may help with GH-1710 --- app/src/main/java/helium314/keyboard/latin/LatinIME.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app/src/main/java/helium314/keyboard/latin/LatinIME.java b/app/src/main/java/helium314/keyboard/latin/LatinIME.java index 917aff58d..f61786c33 100644 --- a/app/src/main/java/helium314/keyboard/latin/LatinIME.java +++ b/app/src/main/java/helium314/keyboard/latin/LatinIME.java @@ -1108,6 +1108,7 @@ public class LatinIME extends InputMethodService implements @Override public void onWindowHidden() { super.onWindowHidden(); + Log.i(TAG, "onWindowHidden"); final MainKeyboardView mainKeyboardView = mKeyboardSwitcher.getMainKeyboardView(); if (mainKeyboardView != null) { mainKeyboardView.closing(); @@ -1210,6 +1211,7 @@ public class LatinIME extends InputMethodService implements @Override public void hideWindow() { + Log.i(TAG, "hideWindow"); if (hasSuggestionStripView() && mSettings.getCurrent().mToolbarMode == ToolbarMode.EXPANDABLE) mSuggestionStripView.setToolbarVisibility(false); mKeyboardSwitcher.onHideWindow(); @@ -1222,6 +1224,12 @@ public class LatinIME extends InputMethodService implements super.hideWindow(); } + @Override + public void requestHideSelf(int flags) { + super.requestHideSelf(flags); + Log.i(TAG, "requestHideSelf: " + flags); + } + @Override public void onDisplayCompletions(final CompletionInfo[] applicationSpecifiedCompletions) { if (DebugFlags.DEBUG_ENABLED) { From 77a728e3903046082763e30e21ac5c78810e2338 Mon Sep 17 00:00:00 2001 From: Helium314 Date: Thu, 19 Jun 2025 09:35:08 +0200 Subject: [PATCH 17/26] don't pre-select unavailable locale when adding dictionary --- .../helium314/keyboard/settings/dialogs/NewDictionaryDialog.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/helium314/keyboard/settings/dialogs/NewDictionaryDialog.kt b/app/src/main/java/helium314/keyboard/settings/dialogs/NewDictionaryDialog.kt index 1405c71ae..a82a07217 100644 --- a/app/src/main/java/helium314/keyboard/settings/dialogs/NewDictionaryDialog.kt +++ b/app/src/main/java/helium314/keyboard/settings/dialogs/NewDictionaryDialog.kt @@ -47,12 +47,12 @@ fun NewDictionaryDialog( } else if (header != null) { val ctx = LocalContext.current val dictLocale = header.mLocaleString.constructLocale() - var locale by remember { mutableStateOf(mainLocale ?: dictLocale) } val enabledLanguages = SubtypeSettings.getEnabledSubtypes().map { it.locale().language } val comparer = compareBy({ it != mainLocale }, { it != dictLocale }, { it.language !in enabledLanguages }, { it.script() != dictLocale.script() }) val locales = SubtypeSettings.getAvailableSubtypeLocales() .filter { it.script() == dictLocale.script() || it.script() == mainLocale?.script() } .sortedWith(comparer) + var locale by remember { mutableStateOf(mainLocale ?: dictLocale.takeIf { it in locales } ?: locales.first()) } val cacheDir = DictionaryInfoUtils.getCacheDirectoryForLocale(locale, ctx) val dictFile = File(cacheDir, header.mIdString.substringBefore(":") + "_" + DictionaryInfoUtils.USER_DICTIONARY_SUFFIX) val type = header.mIdString.substringBefore(":") From 76ebf999213ec97727fc0f5a8152d4b55b8e7061 Mon Sep 17 00:00:00 2001 From: Helium314 Date: Sun, 22 Jun 2025 12:11:20 +0200 Subject: [PATCH 18/26] deal with Android refusing to add word to user dictionary fixes GH-1735 --- .../helium314/keyboard/latin/DictionaryFacilitatorImpl.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/helium314/keyboard/latin/DictionaryFacilitatorImpl.kt b/app/src/main/java/helium314/keyboard/latin/DictionaryFacilitatorImpl.kt index 325ee3595..02ae0b436 100644 --- a/app/src/main/java/helium314/keyboard/latin/DictionaryFacilitatorImpl.kt +++ b/app/src/main/java/helium314/keyboard/latin/DictionaryFacilitatorImpl.kt @@ -397,7 +397,10 @@ class DictionaryFacilitatorImpl : DictionaryFacilitator { // This is not too bad, but it delays adding in case a user wants to fill a dictionary using this functionality if (userHistoryDict.getFrequency(word) > 120) { scope.launch { - UserDictionary.Words.addWord(userDict.mContext, word, 250, null, dictionaryGroup.locale) + // adding can throw IllegalArgumentException: Unknown URL content://user_dictionary/words + // https://stackoverflow.com/q/41474623 https://github.com/AnySoftKeyboard/AnySoftKeyboard/issues/490 + // apparently some devices don't have a dictionary? or it's just sporadic hiccups? + runCatching { UserDictionary.Words.addWord(userDict.mContext, word, 250, null, dictionaryGroup.locale) } } } } From 9193c95c2b386744c3a49dac359be40d5189a9e1 Mon Sep 17 00:00:00 2001 From: Eran Leshem <1707552+eranl@users.noreply.github.com> Date: Sun, 22 Jun 2025 13:18:37 +0300 Subject: [PATCH 19/26] Ignore spacer columns when calculating emoji page size (#1721) --- .../keyboard/keyboard/emoji/DynamicGridKeyboard.java | 6 +++--- .../helium314/keyboard/keyboard/emoji/EmojiCategory.java | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/helium314/keyboard/keyboard/emoji/DynamicGridKeyboard.java b/app/src/main/java/helium314/keyboard/keyboard/emoji/DynamicGridKeyboard.java index 6e9c4a36c..eea566a72 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/emoji/DynamicGridKeyboard.java +++ b/app/src/main/java/helium314/keyboard/keyboard/emoji/DynamicGridKeyboard.java @@ -112,12 +112,12 @@ final class DynamicGridKeyboard extends Keyboard { } public int getDynamicOccupiedHeight() { - final int row = (mGridKeys.size() - 1) / mColumnsNum + 1; + final int row = (mGridKeys.size() - 1) / getOccupiedColumnCount() + 1; return row * mVerticalStep; } - public int getColumnsCount() { - return mColumnsNum; + public int getOccupiedColumnCount() { + return mColumnsNum - mEmptyColumnIndices.size(); } public void addPendingKey(final Key usedKey) { diff --git a/app/src/main/java/helium314/keyboard/keyboard/emoji/EmojiCategory.java b/app/src/main/java/helium314/keyboard/keyboard/emoji/EmojiCategory.java index d2a7213fd..413a6b8f6 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/emoji/EmojiCategory.java +++ b/app/src/main/java/helium314/keyboard/keyboard/emoji/EmojiCategory.java @@ -324,7 +324,7 @@ final class EmojiCategory { final DynamicGridKeyboard tempKeyboard = new DynamicGridKeyboard(mPrefs, mLayoutSet.getKeyboard(KeyboardId.ELEMENT_EMOJI_RECENTS), 0, 0, ResourceUtils.getKeyboardWidth(mContext, Settings.getValues())); - return MAX_LINE_COUNT_PER_PAGE * tempKeyboard.getColumnsCount(); + return MAX_LINE_COUNT_PER_PAGE * tempKeyboard.getOccupiedColumnCount(); } private static final Comparator EMOJI_KEY_COMPARATOR = (lhs, rhs) -> { From c2068224a0004843d2f5e1769f8595285443c7f5 Mon Sep 17 00:00:00 2001 From: Eran Leshem <1707552+eranl@users.noreply.github.com> Date: Sun, 22 Jun 2025 13:23:46 +0300 Subject: [PATCH 20/26] Change settings search keyboard action to `Search`. (#1734) --- .../main/java/helium314/keyboard/settings/SearchScreen.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/helium314/keyboard/settings/SearchScreen.kt b/app/src/main/java/helium314/keyboard/settings/SearchScreen.kt index 66ba49f64..16b2e2d92 100644 --- a/app/src/main/java/helium314/keyboard/settings/SearchScreen.kt +++ b/app/src/main/java/helium314/keyboard/settings/SearchScreen.kt @@ -16,6 +16,7 @@ import androidx.compose.foundation.layout.safeDrawing import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.verticalScroll import androidx.compose.material3.DropdownMenu import androidx.compose.material3.DropdownMenuItem @@ -45,6 +46,7 @@ import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.unit.dp import helium314.keyboard.latin.R @@ -241,7 +243,8 @@ fun ExpandableSearchField( }) { CloseIcon(android.R.string.cancel) } }, singleLine = true, colors = colors, - textStyle = contentTextDirectionStyle + textStyle = contentTextDirectionStyle, + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search) ) } } From 53a899794eeaa35e4d3c4c54a9d59f90b80ede28 Mon Sep 17 00:00:00 2001 From: Helium314 Date: Tue, 24 Jun 2025 06:27:51 +0200 Subject: [PATCH 21/26] register OnSharedPreferenceChangeListener in LatinIME.onCreate because it's unregistered in onDestroy (registering twice is not an issue) though maybe not unregistering would be more correct, as it's registered in app.onCreate? fixes GH-1670 --- app/src/main/java/helium314/keyboard/latin/LatinIME.java | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/java/helium314/keyboard/latin/LatinIME.java b/app/src/main/java/helium314/keyboard/latin/LatinIME.java index f61786c33..ea253bf0c 100644 --- a/app/src/main/java/helium314/keyboard/latin/LatinIME.java +++ b/app/src/main/java/helium314/keyboard/latin/LatinIME.java @@ -626,6 +626,7 @@ public class LatinIME extends InputMethodService implements @Override public void onCreate() { + mSettings.startListener(); KeyboardIconsSet.Companion.getInstance().loadIcons(this); mRichImm = RichInputMethodManager.getInstance(); AudioAndHapticFeedbackManager.init(this); From 945a700b713edfb82407d3b2216552204af7f6ae Mon Sep 17 00:00:00 2001 From: Helium314 Date: Sat, 28 Jun 2025 14:11:00 +0200 Subject: [PATCH 22/26] do the correct checks when loading blacklist file fixes GH-1475 --- .../java/helium314/keyboard/latin/DictionaryFacilitatorImpl.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/helium314/keyboard/latin/DictionaryFacilitatorImpl.kt b/app/src/main/java/helium314/keyboard/latin/DictionaryFacilitatorImpl.kt index 02ae0b436..c64158b01 100644 --- a/app/src/main/java/helium314/keyboard/latin/DictionaryFacilitatorImpl.kt +++ b/app/src/main/java/helium314/keyboard/latin/DictionaryFacilitatorImpl.kt @@ -734,7 +734,7 @@ private class DictionaryGroup( else { val file = File(context.filesDir.absolutePath + File.separator + "blacklists" + File.separator + locale.toLanguageTag() + ".txt") if (file.isDirectory) file.delete() // this apparently was an issue in some versions - if (file.parentFile?.mkdirs() == true) file + if (file.parentFile?.exists() == true || file.parentFile?.mkdirs() == true) file else null } From 8d876a15f0e17c896ddc039d67fbf8630f1adbbb Mon Sep 17 00:00:00 2001 From: Helium314 Date: Sat, 28 Jun 2025 15:16:25 +0200 Subject: [PATCH 23/26] fix crash in isShowingKeyboardId with hardware keyboard --- .../java/helium314/keyboard/keyboard/KeyboardSwitcher.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/helium314/keyboard/keyboard/KeyboardSwitcher.java b/app/src/main/java/helium314/keyboard/keyboard/KeyboardSwitcher.java index 9f8796353..34330ad48 100644 --- a/app/src/main/java/helium314/keyboard/keyboard/KeyboardSwitcher.java +++ b/app/src/main/java/helium314/keyboard/keyboard/KeyboardSwitcher.java @@ -601,7 +601,10 @@ public final class KeyboardSwitcher implements KeyboardState.SwitchActions { if (mKeyboardView == null || !mKeyboardView.isShown()) { return false; } - int activeKeyboardId = mKeyboardView.getKeyboard().mId.mElementId; + final Keyboard keyboard = mKeyboardView.getKeyboard(); + if (keyboard == null) // may happen when using hardware keyboard + return false; + int activeKeyboardId = keyboard.mId.mElementId; for (int keyboardId : keyboardIds) { if (activeKeyboardId == keyboardId) { return true; From 10a5eab3bc52e6ba15f87f88afee97169bbdc449 Mon Sep 17 00:00:00 2001 From: Helium314 Date: Sat, 28 Jun 2025 16:20:54 +0200 Subject: [PATCH 24/26] don't change insets when view is hidden by hardware keyboard seems to happen only in some apps, e.g. simplemobiletools notes issue only occurs when keyboard is suppressed after it has been shown and a new input view is created fixes GH-1455 fixes GH-702 --- app/src/main/java/helium314/keyboard/latin/LatinIME.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/helium314/keyboard/latin/LatinIME.java b/app/src/main/java/helium314/keyboard/latin/LatinIME.java index ea253bf0c..7ddfdc2cf 100644 --- a/app/src/main/java/helium314/keyboard/latin/LatinIME.java +++ b/app/src/main/java/helium314/keyboard/latin/LatinIME.java @@ -1282,8 +1282,8 @@ public class LatinIME extends InputMethodService implements if (isImeSuppressedByHardwareKeyboard() && !visibleKeyboardView.isShown()) { // If there is a hardware keyboard and a visible software keyboard view has been hidden, // no visual element will be shown on the screen. - outInsets.contentTopInsets = inputHeight; - outInsets.visibleTopInsets = inputHeight; + // for some reason setting contentTopInsets and visibleTopInsets broke somewhere along the + // way from OpenBoard to HeliBoard (GH-702, GH-1455), but not setting anything seems to work mInsetsUpdater.setInsets(outInsets); return; } From 4f58e5d0137c138b294a041c80f6cc0e499e76d1 Mon Sep 17 00:00:00 2001 From: Eran Leshem <1707552+eranl@users.noreply.github.com> Date: Sat, 28 Jun 2025 17:25:15 +0300 Subject: [PATCH 25/26] Support Latin (#1749) --- app/src/main/res/xml/method.xml | 12 +++++++++++- app/src/main/res/xml/spellchecker.xml | 3 +++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/xml/method.xml b/app/src/main/res/xml/method.xml index 0a8e99fc9..69994344c 100644 --- a/app/src/main/res/xml/method.xml +++ b/app/src/main/res/xml/method.xml @@ -79,6 +79,7 @@ kn_IN: Kannada (India)/kannada kn_IN: Kannada Extended (India)/kannada ky: Kyrgyz/russian + la: Latin lg: Luganda/luganda # This is a preliminary keyboard layout. ln: Lingala/lingala # This is a preliminary keyboard layout. lo_LA: Lao (Laos)/lao @@ -828,6 +829,15 @@ android:imeSubtypeMode="keyboard" android:imeSubtypeExtraValue="KeyboardLayoutSet=MAIN:qwertz,AsciiCapable,EmojiCapable" /> + + From d2217f099a1efbfea036bdc652fe51c93cd8a8a5 Mon Sep 17 00:00:00 2001 From: Eran Leshem <1707552+eranl@users.noreply.github.com> Date: Sun, 29 Jun 2025 21:02:29 +0300 Subject: [PATCH 26/26] Export `DictionaryPackInstallBroadcastReceiver` (#1756) --- app/src/main/java/helium314/keyboard/latin/LatinIME.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/helium314/keyboard/latin/LatinIME.java b/app/src/main/java/helium314/keyboard/latin/LatinIME.java index 7ddfdc2cf..9e2ed3c72 100644 --- a/app/src/main/java/helium314/keyboard/latin/LatinIME.java +++ b/app/src/main/java/helium314/keyboard/latin/LatinIME.java @@ -654,7 +654,8 @@ public class LatinIME extends InputMethodService implements final IntentFilter newDictFilter = new IntentFilter(); newDictFilter.addAction(DictionaryPackConstants.NEW_DICTIONARY_INTENT_ACTION); - ContextCompat.registerReceiver(this, mDictionaryPackInstallReceiver, newDictFilter, ContextCompat.RECEIVER_NOT_EXPORTED); + // RECEIVER_EXPORTED is necessary because apparently Android 15 (and others?) don't recognize if the sender and receiver are the same app, see https://github.com/Helium314/HeliBoard/pull/1756 + ContextCompat.registerReceiver(this, mDictionaryPackInstallReceiver, newDictFilter, ContextCompat.RECEIVER_EXPORTED); final IntentFilter dictDumpFilter = new IntentFilter(); dictDumpFilter.addAction(DictionaryDumpBroadcastReceiver.DICTIONARY_DUMP_INTENT_ACTION);