Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
66 commits
Select commit Hold shift + click to select a range
3eb2f45
Add emoji search
eranl Jul 6, 2025
23426c0
respect _Default emoji skin tone_
eranl Jul 6, 2025
197b84b
Minor fix
eranl Jul 7, 2025
408c893
Minor fix
eranl Jul 7, 2025
494f76b
Minor fix
eranl Jul 8, 2025
7679d3e
Styling
eranl Jul 9, 2025
fcd91f4
Minor fixes
eranl Jul 10, 2025
2725df2
Minor fix
eranl Jul 10, 2025
4a6a51d
Minor fix
eranl Jul 11, 2025
e438011
Minor fix, license
eranl Jul 11, 2025
251e5e8
Minor fix
eranl Jul 12, 2025
2445a23
Minor fixes
eranl Jul 14, 2025
7e005d3
Use `hintLocales`
eranl Jul 18, 2025
00b196c
fix build
eranl Jul 18, 2025
763d247
Cosmetics,
eranl Jul 19, 2025
ee1ce7c
Fix crash on IME switch
eranl Jul 22, 2025
a855413
Minor fix
eranl Jul 26, 2025
485783a
Add timing logs,
eranl Jul 28, 2025
4d381c3
Fix search startup instability,
eranl Jul 30, 2025
26972b0
Fix failing tests
eranl Jul 30, 2025
c6c1e2d
Merge branch 'refs/heads/main' into emoji-search
eranl Aug 9, 2025
d1042dc
Minor improvements
eranl Aug 9, 2025
d58f7a2
Minor fix
eranl Aug 15, 2025
cece516
Minor fix
eranl Aug 16, 2025
104a33e
Minor fix
eranl Aug 16, 2025
47cbf8a
Merge branch 'refs/heads/main' into emoji-search
eranl Aug 21, 2025
dd37915
Another activity closing fix,
eranl Aug 21, 2025
534191a
Another activity closing fix
eranl Aug 23, 2025
885f45b
Use `isEmoji`
eranl Sep 24, 2025
f7ec68f
Add todo
eranl Sep 27, 2025
876c5e8
Cleanup
eranl Nov 1, 2025
cba98d5
Merge branch 'refs/heads/main' into emoji-search
eranl Nov 4, 2025
b90fed3
Fix main merge
eranl Nov 4, 2025
3f55c09
Fix main merge
eranl Nov 4, 2025
76f768c
Switch to abc keyboard
eranl Nov 4, 2025
8876038
Fix autocorrect
eranl Nov 5, 2025
61a0ef9
Merge branch 'refs/heads/main' into emoji-search
eranl Nov 14, 2025
7bc00c8
Disable inline emoji search if in full emoji search mode
eranl Nov 14, 2025
0a8fbca
Merge branch 'refs/heads/main' into emoji-search
eranl Nov 14, 2025
5058280
Merge branch 'refs/heads/main' into emoji-search
eranl Nov 15, 2025
7ced319
Fix main merge
eranl Nov 15, 2025
00461df
Cleanup
eranl Nov 16, 2025
fdecdfd
Merge branch 'refs/heads/main' into emoji-search
eranl Nov 16, 2025
3c62b49
Merge branch 'main' into emoji-search
eranl Nov 16, 2025
d108ef9
Close dictionary on dictionary changes
eranl Nov 18, 2025
8e04774
Merge branch 'refs/heads/main' into emoji-search
eranl Nov 23, 2025
c9cb50b
Fix for API < 30
eranl Nov 24, 2025
d47b606
Fix landscape
eranl Nov 26, 2025
d8dcff0
Revert `config_emoji_keyboard_max_recents_key_count` -> `config_emoj…
eranl Nov 26, 2025
93c46ad
Hide search button if no emoji dictionary
eranl Nov 26, 2025
ff13e47
Use `keyboard_state_selector`
eranl Dec 1, 2025
5e1fe8f
Merge branch 'main' into emoji-search
Helium314 Feb 21, 2026
c7e9d46
Avoid potential NPE when inserting emoji from search
Helium314 Feb 21, 2026
31e5afc
switch 3-line emoji keyboard to key count...
Helium314 Feb 21, 2026
2acf559
... and make OccupiedHeight dynamic if we use key count
Helium314 Feb 21, 2026
413bf80
add comment
Helium314 Feb 21, 2026
54038dc
rename search key
Helium314 Feb 21, 2026
418d1d7
rename key
Helium314 Feb 21, 2026
3e96251
rename key
Helium314 Feb 21, 2026
f832bb4
rename key
Helium314 Feb 21, 2026
96af525
rename key
Helium314 Feb 21, 2026
f0ec086
rename key
Helium314 Feb 21, 2026
c0a5fc7
rename key
Helium314 Feb 21, 2026
2fa6e3d
rename key
Helium314 Feb 21, 2026
180e18d
rename key
Helium314 Feb 21, 2026
ab67778
rename key
Helium314 Feb 21, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,10 @@ SPDX-License-Identifier: Apache-2.0 AND GPL-3.0-only
</intent-filter>
</activity>

<activity android:name="helium314.keyboard.keyboard.emoji.EmojiSearchActivity" android:exported="false"
android:theme="@style/TransparentActivityTheme" android:excludeFromRecents="true" android:noHistory="true"
android:windowSoftInputMode="adjustResize|stateAlwaysVisible" android:configChanges="orientation|screenSize"/>

<!-- Broadcast receivers -->
<receiver android:name="SystemBroadcastReceiver"
android:exported="true">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
[
[
{ "label": "alpha", "width": 0.15 },
{ "$": "keyboard_state_selector", "emojiSearchAvailable": { "label": "emoji_search", "width": 0.15 }},
{ "label": "space", "width": -1 },
{ "label": "delete", "width": 0.15 }
]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
[
[
{ "label": "alpha", "width": 0.15 },
{ "$": "keyboard_state_selector", "emojiSearchAvailable": { "label": "emoji_search", "width": 0.15 }},
{ "label": "space", "width": -1 },
{ "label": "delete", "width": 0.15 },
{ "label": "action", "width": 0.15 }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ internal class KeyCodeDescriptionMapper private constructor() {
put(KeyCode.ACTION_NEXT, R.string.spoken_description_action_next)
put(KeyCode.ACTION_PREVIOUS, R.string.spoken_description_action_previous)
put(KeyCode.EMOJI, R.string.spoken_description_emoji)
put(KeyCode.EMOJI_SEARCH, R.string.spoken_description_search)
// Because the upper-case and lower-case mappings of the following letters is depending on
// the locale, the upper case descriptions should be defined here. The lower case
// descriptions are handled in {@link #getSpokenLetterDescriptionId(Context,int)}.
Expand Down
3 changes: 2 additions & 1 deletion app/src/main/java/helium314/keyboard/keyboard/Key.java
Original file line number Diff line number Diff line change
Expand Up @@ -1167,7 +1167,8 @@ public KeyParams(
// fallthrough
case KeyCode.SHIFT, Constants.CODE_ENTER, KeyCode.SHIFT_ENTER, KeyCode.ALPHA, Constants.CODE_SPACE, KeyCode.NUMPAD,
KeyCode.SYMBOL, KeyCode.SYMBOL_ALPHA, KeyCode.LANGUAGE_SWITCH, KeyCode.EMOJI, KeyCode.CLIPBOARD,
KeyCode.MOVE_START_OF_LINE, KeyCode.MOVE_END_OF_LINE, KeyCode.MOVE_START_OF_PAGE, KeyCode.MOVE_END_OF_PAGE:
KeyCode.MOVE_START_OF_LINE, KeyCode.MOVE_END_OF_LINE, KeyCode.MOVE_START_OF_PAGE, KeyCode.MOVE_END_OF_PAGE,
KeyCode.EMOJI_SEARCH:
actionFlags |= ACTION_FLAGS_NO_KEY_PREVIEW; // no preview even if icon!
}
if (mCode == KeyCode.SETTINGS || mCode == KeyCode.LANGUAGE_SWITCH)
Expand Down
10 changes: 7 additions & 3 deletions app/src/main/java/helium314/keyboard/keyboard/KeyboardId.java
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ public final class KeyboardId {
public static final int ELEMENT_EMOJI_CATEGORY13 = 23;
public static final int ELEMENT_EMOJI_CATEGORY14 = 24;
public static final int ELEMENT_EMOJI_CATEGORY15 = 25;
public static final int ELEMENT_EMOJI_CATEGORY16 = 26;
public static final int ELEMENT_EMOJI_CATEGORY16 = 26; // Emoji search
public static final int ELEMENT_CLIPBOARD = 27;
public static final int ELEMENT_NUMPAD = 28;
public static final int ELEMENT_EMOJI_BOTTOM_ROW = 29;
Expand All @@ -84,6 +84,7 @@ public final class KeyboardId {
public final boolean mIsSplitLayout;
public final boolean mOneHandedModeEnabled;
public final KeyboardLayoutSet.InternalAction mInternalAction;
public final boolean mEmojiSearchAvailable;

private final int mHashCode;

Expand All @@ -105,6 +106,7 @@ public KeyboardId(final int elementId, final KeyboardLayoutSet.Params params) {
mIsSplitLayout = params.mIsSplitLayoutEnabled;
mOneHandedModeEnabled = params.mOneHandedModeEnabled;
mInternalAction = params.mInternalAction;
mEmojiSearchAvailable = params.mEmojiSearchAvailable;

mHashCode = computeHashCode(this);
}
Expand Down Expand Up @@ -229,7 +231,7 @@ public int hashCode() {

@Override
public String toString() {
return String.format(Locale.ROOT, "[%s %s:%s %dx%d %s %s%s%s%s%s%s%s%s%s%s%s]",
return String.format(Locale.ROOT, "[%s %s:%s %dx%d %s %s%s%s%s%s%s%s%s%s%s%s%s%s]",
elementIdToName(mElementId),
mSubtype.getLocale(),
mSubtype.getExtraValueOf(KEYBOARD_LAYOUT_SET),
Expand All @@ -245,7 +247,9 @@ public String toString() {
(mLanguageSwitchKeyEnabled ? " languageSwitchKeyEnabled" : ""),
(mEmojiKeyEnabled ? " emojiKeyEnabled" : ""),
(isMultiLine() ? " isMultiLine" : ""),
(mIsSplitLayout ? " isSplitLayout" : "")
(mIsSplitLayout ? " isSplitLayout" : ""),
(mInternalAction != null ? " internalAction=" + mInternalAction : ""),
(mEmojiSearchAvailable ? " emojiSearchAvailable" : "")
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import helium314.keyboard.latin.RichInputMethodManager;
import helium314.keyboard.latin.RichInputMethodSubtype;
import helium314.keyboard.latin.settings.Settings;
import helium314.keyboard.latin.utils.DictionaryInfoUtils;
import helium314.keyboard.latin.utils.InputTypeUtils;
import helium314.keyboard.latin.utils.Log;
import helium314.keyboard.latin.utils.ResourceUtils;
Expand Down Expand Up @@ -99,6 +100,7 @@ public static final class Params {
// and the required ProductionFlags are enabled.
boolean mIsSplitLayoutEnabled;
InternalAction mInternalAction;
boolean mEmojiSearchAvailable;
}

public static void onSystemLocaleChanged() {
Expand Down Expand Up @@ -221,6 +223,7 @@ public Builder(final Context context, @Nullable final EditorInfo ei) {
public static KeyboardLayoutSet buildEmojiClipBottomRow(final Context context, @Nullable final EditorInfo ei) {
final Builder builder = new Builder(context, ei);
builder.mParams.mMode = KeyboardId.MODE_TEXT;
builder.mParams.mEmojiSearchAvailable = ! DictionaryInfoUtils.getLocalesWithEmojiDicts(context).isEmpty();
final int width = ResourceUtils.getKeyboardWidth(context, Settings.getValues());
// actually the keyboard does not have full height, but at this point we use it to get correct key heights
final int height = ResourceUtils.getKeyboardHeight(context.getResources(), Settings.getValues());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,9 @@ public void updateKeyboardTheme(@NonNull Context displayContext) {
settings.loadSettings(displayContext, settings.getCurrent().mLocale, settings.getCurrent().mInputAttributes);
if (mKeyboardView != null)
mLatinIME.setInputView(onCreateInputView(displayContext, mIsHardwareAcceleratedDrawingEnabled));
} else if (mCurrentInputView != null && mLatinIME.hasSuggestionStripView()
== (Settings.getValues().mToolbarMode == ToolbarMode.HIDDEN || mLatinIME.isEmojiSearch())) {
mLatinIME.updateSuggestionStripView(mCurrentInputView);
}
}

Expand Down Expand Up @@ -317,7 +320,7 @@ private void setMainKeyboardFrame(
@NonNull final SettingsValues settingsValues,
@NonNull final KeyboardSwitchState toggleState) {
final int visibility = isImeSuppressedByHardwareKeyboard(settingsValues, toggleState) ? View.GONE : View.VISIBLE;
final int stripVisibility = settingsValues.mToolbarMode == ToolbarMode.HIDDEN ? View.GONE : View.VISIBLE;
final int stripVisibility = mLatinIME.hasSuggestionStripView()? View.VISIBLE : View.GONE;
mStripContainer.setVisibility(stripVisibility);
PointerTracker.switchTo(mKeyboardView);
mKeyboardView.setVisibility(visibility);
Expand Down Expand Up @@ -639,6 +642,10 @@ public boolean isShowingStripContainer() {
return mStripContainer.isShown();
}

public EmojiPalettesView getEmojiPalettesView() {
return mEmojiPalettesView;
}

public View getVisibleKeyboardView() {
if (isShowingEmojiPalettes()) {
return mEmojiPalettesView;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,15 +43,26 @@ final class DynamicGridKeyboard extends Keyboard {
private final int mVerticalStep;
private final int mColumnsNum;
private final int mMaxKeyCount;
private final boolean mFixedRowCount;
private final boolean mIsRecents;
private final ArrayDeque<GridKey> mGridKeys = new ArrayDeque<>();
private final ArrayDeque<Key> mPendingKeys = new ArrayDeque<>();

private List<Key> mCachedGridKeys;
private final ArrayList<Integer> mEmptyColumnIndices = new ArrayList<>(4);

public DynamicGridKeyboard(final SharedPreferences prefs, final Keyboard templateKeyboard,
public static DynamicGridKeyboard ofKeyCount(final SharedPreferences prefs, final Keyboard templateKeyboard,
final int maxKeyCount, final int categoryId, final int width) {
return new DynamicGridKeyboard(prefs, templateKeyboard, maxKeyCount, categoryId, width, false);
}

public static DynamicGridKeyboard ofRowCount(final SharedPreferences prefs, final Keyboard templateKeyboard,
final int maxRowCount, final int categoryId, final int width) {
return new DynamicGridKeyboard(prefs, templateKeyboard, maxRowCount, categoryId, width, true);
}

private DynamicGridKeyboard(final SharedPreferences prefs, final Keyboard templateKeyboard,
final int maxCount, final int categoryId, final int width, boolean fixedRowCount) {
super(templateKeyboard);
// todo: would be better to keep them final and not require width, but how to properly set width of the template keyboard?
// an alternative would be to always create the templateKeyboard with full width
Expand All @@ -69,7 +80,8 @@ public DynamicGridKeyboard(final SharedPreferences prefs, final Keyboard templat
mColumnsNum = mBaseWidth / mHorizontalStep;
if (spacerWidth > 0)
setSpacerColumns(spacerWidth);
mMaxKeyCount = maxKeyCount;
mMaxKeyCount = fixedRowCount? maxCount * getOccupiedColumnCount() : maxCount;
mFixedRowCount = fixedRowCount;
mIsRecents = categoryId == EmojiCategory.ID_RECENTS;
mPrefs = prefs;
}
Expand Down Expand Up @@ -111,8 +123,10 @@ private Key getTemplateKey(final int code) {
throw new RuntimeException("Can't find template key: code=" + code);
}

public int getDynamicOccupiedHeight() {
final int row = (mGridKeys.size() - 1) / getOccupiedColumnCount() + 1;
// height is dynamic if we don't have a fixed row count
int getOccupiedHeight() {
final int count = mFixedRowCount ? mMaxKeyCount : mGridKeys.size();
final int row = (count - 1) / getOccupiedColumnCount() + 1;
return row * mVerticalStep;
}

Expand Down Expand Up @@ -146,6 +160,13 @@ public void addKeyLast(final Key usedKey) {
addKey(usedKey, false);
}

public void removeAllKeys() {
synchronized (mLock) {
mGridKeys.clear();
mCachedGridKeys = null;
}
}

private void addKey(final Key usedKey, final boolean addFirst) {
if (usedKey == null) {
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,7 @@ public DynamicGridKeyboard getKeyboard(final int categoryId, final int id) {

final int currentWidth = ResourceUtils.getKeyboardWidth(mContext, Settings.getValues());
if (categoryId == EmojiCategory.ID_RECENTS) {
final DynamicGridKeyboard kbd = new DynamicGridKeyboard(mPrefs,
final DynamicGridKeyboard kbd = DynamicGridKeyboard.ofKeyCount(mPrefs,
mLayoutSet.getKeyboard(KeyboardId.ELEMENT_EMOJI_RECENTS),
mMaxRecentsKeyCount, categoryId, currentWidth);
mCategoryKeyboardMap.put(categoryKeyboardMapKey, kbd);
Expand All @@ -305,7 +305,7 @@ public DynamicGridKeyboard getKeyboard(final int categoryId, final int id) {
final Key[][] sortedKeysPages = sortKeysGrouped(
keyboard.getSortedKeys(), keyCountPerPage);
for (int pageId = 0; pageId < sortedKeysPages.length; ++pageId) {
final DynamicGridKeyboard tempKeyboard = new DynamicGridKeyboard(mPrefs,
final DynamicGridKeyboard tempKeyboard = DynamicGridKeyboard.ofKeyCount(mPrefs,
mLayoutSet.getKeyboard(KeyboardId.ELEMENT_EMOJI_RECENTS),
keyCountPerPage, categoryId, currentWidth);
for (final Key emojiKey : sortedKeysPages[pageId]) {
Expand All @@ -321,7 +321,7 @@ public DynamicGridKeyboard getKeyboard(final int categoryId, final int id) {
}

private int computeMaxKeyCountPerPage() {
final DynamicGridKeyboard tempKeyboard = new DynamicGridKeyboard(mPrefs,
final DynamicGridKeyboard tempKeyboard = DynamicGridKeyboard.ofKeyCount(mPrefs,
mLayoutSet.getKeyboard(KeyboardId.ELEMENT_EMOJI_RECENTS),
0, 0, ResourceUtils.getKeyboardWidth(mContext, Settings.getValues()));
return MAX_LINE_COUNT_PER_PAGE * tempKeyboard.getOccupiedColumnCount();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,14 +113,15 @@ public EmojiPageKeyboardView(final Context context, final AttributeSet attrs,
mPopupKeysKeyboardContainer = inflater.inflate(popupKeysKeyboardLayoutId, null);
mDescriptionView = mPopupKeysKeyboardContainer.findViewById(R.id.description_view);
mPopupKeysKeyboardView = mPopupKeysKeyboardContainer.findViewById(R.id.popup_keys_keyboard_view);
setFitsSystemWindows(false);
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final Keyboard keyboard = getKeyboard();
if (keyboard instanceof DynamicGridKeyboard) {
final int width = keyboard.mOccupiedWidth + getPaddingLeft() + getPaddingRight();
final int occupiedHeight = ((DynamicGridKeyboard) keyboard).getDynamicOccupiedHeight();
final int occupiedHeight = ((DynamicGridKeyboard) keyboard).getOccupiedHeight();
final int height = occupiedHeight + getPaddingTop() + getPaddingBottom();
setMeasuredDimension(width, height);
return;
Expand Down Expand Up @@ -437,23 +438,25 @@ public boolean onMove(final MotionEvent e) {
final int x = (int)e.getX();
final int y = (int)e.getY();
final Key key = getKey(x, y);
final boolean isShowingPopupKeysPanel = isShowingPopupKeysPanel();
final boolean isShowingPopupKeyboard = isShowingPopupKeysPanel() && mPopupKeysKeyboardView.getVisibility() == VISIBLE;

// Touched key has changed, release previous key's callbacks and
// re-register them for the new key.
if (key != mCurrentKey && !isShowingPopupKeysPanel) {
if (key != mCurrentKey && !isShowingPopupKeyboard) {
releaseCurrentKey(false);
mCurrentKey = key;
cancelLongPress();
if (isShowingPopupKeysPanel()) {
onCancelPopupKeysPanel();
}
if (key == null) {
return false;
}
registerPress(key);

cancelLongPress();
registerLongPress(key);
}

if (isShowingPopupKeysPanel) {
if (isShowingPopupKeyboard) {
final long eventTime = e.getEventTime();
final int translatedX = mPopupKeysPanel.translateX(x);
final int translatedY = mPopupKeysPanel.translateY(y);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
import helium314.keyboard.keyboard.PointerTracker;
import helium314.keyboard.keyboard.internal.KeyDrawParams;
import helium314.keyboard.keyboard.internal.KeyVisualAttributes;
import helium314.keyboard.keyboard.internal.keyboard_parser.EmojiParserKt;
import helium314.keyboard.keyboard.internal.keyboard_parser.floris.KeyCode;
import helium314.keyboard.latin.AudioAndHapticFeedbackManager;
import helium314.keyboard.latin.dictionary.Dictionary;
Expand Down Expand Up @@ -318,7 +319,7 @@ public String getDescription(String emoji) {
return null;
}

var wordProperty = sDictionaryFacilitator.getWordProperty(emoji);
var wordProperty = sDictionaryFacilitator.getWordProperty(EmojiParserKt.getEmojiNeutralVersion(emoji));
if (wordProperty == null || ! wordProperty.mHasShortcuts) {
return null;
}
Expand All @@ -343,17 +344,18 @@ public void startEmojiPalettes(final KeyVisualAttributes keyVisualAttr,
initDictionaryFacilitator();
}

private void addRecentKey(final Key key) {
void addRecentKey(final Key key) {
if (Settings.getValues().mIncognitoModeEnabled) {
// We do not want to log recent keys while being in incognito
return;
}
if (mEmojiCategory.isInRecentTab()) {
if (getVisibility() == VISIBLE && mEmojiCategory.isInRecentTab()) {
getRecentsKeyboard().addPendingKey(key);
return;
}
getRecentsKeyboard().addKeyFirst(key);
mPager.getAdapter().notifyItemChanged(mEmojiCategory.getRecentTabId());
if (initialized)
mPager.getAdapter().notifyItemChanged(mEmojiCategory.getRecentTabId());
}

private void setupBottomRowKeyboard(final EditorInfo editorInfo, final KeyboardActionListener keyboardActionListener) {
Expand Down
Loading