Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
12 changes: 12 additions & 0 deletions app/src/main/java/helium314/keyboard/latin/WordComposer.java
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,18 @@ public void applyProcessedEvent(final Event event, final boolean keepCursorPosit
// shouldn't be overridden by the "typed key" coordinates
// (See {@link #setBatchInputWord}).
if (!mIsBatchMode) {
// Two-thumb / multi-part: the FIRST tap of a fresh word resets the raw stroke
// buffer. mInputPointers is intentionally NOT cleared by reset() or backspace
// (it must survive setBatchInputWord so a gesture commit can re-feed it), and
// addPointerAt overwrites in place WITHOUT shrinking the length — so a word
// abandoned via backspace leaves a stale tail. Without this, re-typing the word
// and swiping would snapshot that stale buffer as the merged-trail extend base
// and build an ever-longer garbage word. Clearing only at index 0 (a brand-new
// word) drops the stale geometry while leaving mid-word taps (newIndex > 0, incl.
// a tap extending a gesture) and the batch-mode gesture trail untouched.
if (newIndex == 0) {
mInputPointers.reset();
}
// TODO: Set correct pointer id and time
mInputPointers.addPointerAt(newIndex, keyX, keyY, 0, 0);
}
Expand Down
34 changes: 34 additions & 0 deletions app/src/test/java/helium314/keyboard/latin/WordComposerTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,40 @@ public void testExtendBatchInputBaseMergesAndRetimes() {
assertTrue("base must end before the new gesture begins", times[1] < times[2]);
}

// #7: the raw stroke buffer (mInputPointers) must not bleed into a fresh word. It is
// intentionally NOT cleared by reset() (so a gesture commit can re-feed it via
// setBatchInputWord), and addPointerAt overwrites in place WITHOUT shrinking the length — so
// after a word is abandoned via backspace, a stale tail survives. The next swipe would then
// snapshot that stale buffer as the merged-trail extend base and build an ever-longer garbage
// word (the "swipes stick around and grow" bug). The first tap of a new word must start clean.
@Test
public void testFreshWordResetsStaleInputPointers() {
final WordComposer wordComposer = new WordComposer();

// Geometry left by a prior (gesture) word: a 5-point stroke.
final InputPointers stale = new InputPointers(16);
stale.addPointer(1, 1, 0, 0);
stale.addPointer(2, 2, 0, 10);
stale.addPointer(3, 3, 0, 20);
stale.addPointer(4, 4, 0, 30);
stale.addPointer(5, 5, 0, 40);
wordComposer.setBatchInputPointers(stale);
assertEquals(5, wordComposer.getInputPointers().getPointerSize());

// Word abandoned (e.g. backspaced). reset() deliberately does NOT clear mInputPointers,
// so the stale 5-point buffer survives — this is the premise of the bug.
wordComposer.reset();
assertEquals(5, wordComposer.getInputPointers().getPointerSize());

// Start a fresh word with a single tapped letter (index 0). The stale buffer must be
// dropped, leaving exactly one point — not 5 (pre-fix: addAt(0) overwrites in place and
// the length stays 5, which is what the following swipe then merged with).
final int[] codePoints = new int[] { 'a' };
final int[] coordinates = CoordinateUtils.newCoordinateArray(1, 50, 60);
wordComposer.setComposingWord(codePoints, coordinates);
assertEquals(1, wordComposer.getInputPointers().getPointerSize());
}

@Test
public void testExtendBatchInputBaseEmptyIsNoOp() {
final WordComposer wordComposer = new WordComposer();
Expand Down
Loading