Skip to content

Accelerated backspace measures the wrong code point and can split an emoji #423

Description

@AsafMah

Summary

In InputLogic.handleBackspaceEvent, the accelerated (held-backspace) second deletion measures its length from the first code point instead of the one it is actually about to delete. When the second character back is a multi-code-point emoji, this deletes the wrong number of chars and can split the emoji, leaving an orphaned surrogate or a stray variation selector / ZWJ.

Present at tag v4.1.2 (f0ff166ae).

Location

app/src/main/java/helium314/keyboard/latin/inputlogic/InputLogic.java, around lines 2019–2026:

final int codePointBeforeCursorToDeleteAgain = mConnection.getCodePointBeforeCursor();
if (codePointBeforeCursorToDeleteAgain != Constants.NOT_A_CODE) {
    final int lengthToDeleteAgain = codePointBeforeCursor > 0xFE00          // <-- first code point
            || StringUtils.mightBeEmoji(codePointBeforeCursor)              // <-- first code point
                    ? mConnection.getCharCountToDeleteBeforeCursor()
                    : 1;
    mConnection.deleteTextBeforeCursor(lengthToDeleteAgain);
    totalDeletedLength += lengthToDeleteAgain;
}

The guard correctly re-reads the cursor into codePointBeforeCursorToDeleteAgain, but the ternary that decides the delete length still tests codePointBeforeCursor — the value from before the first deletion.

Consequences

Both directions are wrong:

  • First char plain, second an emoji → the branch evaluates false, so lengthToDeleteAgain is 1. A single char is removed from a multi-char emoji, splitting it.
  • First char an emoji, second plain → the branch evaluates true, so getCharCountToDeleteBeforeCursor() is used against a plain character and may remove more than intended.

Only reachable once mDeleteCount > Constants.DELETE_ACCELERATE_AT, i.e. while holding backspace, which is probably why it has gone unnoticed.

Suggested fix

Use the re-read value in the condition, so it describes the character actually being deleted:

final int lengthToDeleteAgain = codePointBeforeCursorToDeleteAgain > 0xFE00
        || StringUtils.mightBeEmoji(codePointBeforeCursorToDeleteAgain)
                ? mConnection.getCharCountToDeleteBeforeCursor()
                : 1;

Notes

Found while merging v4.1.2 into a downstream fork. This almost certainly predates LeanType — the surrounding accelerated-delete code comes from the HeliBoard/AOSP lineage — so it may be worth forwarding to Helium314/HeliBoard as well.

I have no reproduction on a physical device; this is from reading the code, so please sanity-check the emoji-splitting claim against real input before acting on it.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions