diff --git a/shell/platform/android/io/flutter/plugin/editing/InputConnectionAdaptor.java b/shell/platform/android/io/flutter/plugin/editing/InputConnectionAdaptor.java index 469d2b465d893..d702e2b7855ea 100644 --- a/shell/platform/android/io/flutter/plugin/editing/InputConnectionAdaptor.java +++ b/shell/platform/android/io/flutter/plugin/editing/InputConnectionAdaptor.java @@ -325,7 +325,22 @@ private static int clampIndexToEditable(int index, Editable editable) { public boolean sendKeyEvent(KeyEvent event) { markDirty(); if (event.getAction() == KeyEvent.ACTION_DOWN) { - if (event.getKeyCode() == KeyEvent.KEYCODE_DPAD_LEFT) { + if (event.getKeyCode() == KeyEvent.KEYCODE_DEL) { + int selStart = clampIndexToEditable(Selection.getSelectionStart(mEditable), mEditable); + int selEnd = clampIndexToEditable(Selection.getSelectionEnd(mEditable), mEditable); + if (selStart == selEnd && selStart > 0) { + // Extend selection to left of the last character + selStart = flutterTextUtils.getOffsetBefore(mEditable, selStart); + } + if (selEnd > selStart) { + // Delete the selection. + Selection.setSelection(mEditable, selStart); + mEditable.delete(selStart, selEnd); + updateEditingState(); + return true; + } + return false; + } else if (event.getKeyCode() == KeyEvent.KEYCODE_DPAD_LEFT) { int selStart = Selection.getSelectionStart(mEditable); int selEnd = Selection.getSelectionEnd(mEditable); if (selStart == selEnd && !event.isShiftPressed()) { diff --git a/shell/platform/android/test/io/flutter/plugin/editing/InputConnectionAdaptorTest.java b/shell/platform/android/test/io/flutter/plugin/editing/InputConnectionAdaptorTest.java index 609b913c66a0c..2052a47934c0d 100644 --- a/shell/platform/android/test/io/flutter/plugin/editing/InputConnectionAdaptorTest.java +++ b/shell/platform/android/test/io/flutter/plugin/editing/InputConnectionAdaptorTest.java @@ -944,18 +944,166 @@ public void inputConnectionAdaptor_RepeatFilter() throws NullPointerException { } @Test - public void testSendKeyEvent_delKeyNotConsumed() { + public void testSendKeyEvent_delKeyDeletesBackward() { int selStart = 29; Editable editable = sampleEditable(selStart, selStart, SAMPLE_RTL_TEXT); InputConnectionAdaptor adaptor = sampleInputConnectionAdaptor(editable); KeyEvent downKeyDown = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL); - for (int i = 0; i < 10; i++) { + for (int i = 0; i < 9; i++) { boolean didConsume = adaptor.sendKeyEvent(downKeyDown); - assertFalse(didConsume); + assertTrue(didConsume); + } + assertEquals(Selection.getSelectionStart(editable), 19); + + for (int i = 0; i < 9; i++) { + boolean didConsume = adaptor.sendKeyEvent(downKeyDown); + assertTrue(didConsume); + } + assertEquals(Selection.getSelectionStart(editable), 10); + } + + @Test + public void testSendKeyEvent_delKeyDeletesBackwardComplexEmojis() { + int selStart = 75; + Editable editable = sampleEditable(selStart, selStart, SAMPLE_EMOJI_TEXT); + InputConnectionAdaptor adaptor = sampleInputConnectionAdaptor(editable); + + KeyEvent downKeyDown = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL); + boolean didConsume; + + // Normal Character + didConsume = adaptor.sendKeyEvent(downKeyDown); + assertTrue(didConsume); + assertEquals(Selection.getSelectionStart(editable), 74); + + // Non-Spacing Mark + didConsume = adaptor.sendKeyEvent(downKeyDown); + assertTrue(didConsume); + assertEquals(Selection.getSelectionStart(editable), 73); + didConsume = adaptor.sendKeyEvent(downKeyDown); + assertTrue(didConsume); + assertEquals(Selection.getSelectionStart(editable), 72); + + // Keycap + didConsume = adaptor.sendKeyEvent(downKeyDown); + assertTrue(didConsume); + assertEquals(Selection.getSelectionStart(editable), 69); + + // Keycap with invalid base + adaptor.setSelection(68, 68); + didConsume = adaptor.sendKeyEvent(downKeyDown); + assertTrue(didConsume); + assertEquals(Selection.getSelectionStart(editable), 66); + adaptor.setSelection(67, 67); + didConsume = adaptor.sendKeyEvent(downKeyDown); + assertTrue(didConsume); + assertEquals(Selection.getSelectionStart(editable), 66); + + // Zero Width Joiner + didConsume = adaptor.sendKeyEvent(downKeyDown); + assertTrue(didConsume); + assertEquals(Selection.getSelectionStart(editable), 55); + + // Zero Width Joiner with invalid base + didConsume = adaptor.sendKeyEvent(downKeyDown); + assertTrue(didConsume); + assertEquals(Selection.getSelectionStart(editable), 53); + didConsume = adaptor.sendKeyEvent(downKeyDown); + assertTrue(didConsume); + assertEquals(Selection.getSelectionStart(editable), 52); + didConsume = adaptor.sendKeyEvent(downKeyDown); + assertTrue(didConsume); + assertEquals(Selection.getSelectionStart(editable), 51); + + // ----- Start Emoji Tag Sequence with invalid base testing ---- + // Delete base tag + adaptor.setSelection(39, 39); + didConsume = adaptor.sendKeyEvent(downKeyDown); + assertTrue(didConsume); + assertEquals(Selection.getSelectionStart(editable), 37); + + // Delete the sequence + adaptor.setSelection(49, 49); + for (int i = 0; i < 6; i++) { + didConsume = adaptor.sendKeyEvent(downKeyDown); + assertTrue(didConsume); } - assertEquals(29, Selection.getSelectionStart(editable)); + assertEquals(Selection.getSelectionStart(editable), 37); + // ----- End Emoji Tag Sequence with invalid base testing ---- + + // Emoji Tag Sequence + didConsume = adaptor.sendKeyEvent(downKeyDown); + assertTrue(didConsume); + assertEquals(Selection.getSelectionStart(editable), 23); + + // Variation Selector with invalid base + adaptor.setSelection(22, 22); + didConsume = adaptor.sendKeyEvent(downKeyDown); + assertTrue(didConsume); + assertEquals(Selection.getSelectionStart(editable), 21); + adaptor.setSelection(22, 22); + didConsume = adaptor.sendKeyEvent(downKeyDown); + assertTrue(didConsume); + assertEquals(Selection.getSelectionStart(editable), 21); + + // Variation Selector + didConsume = adaptor.sendKeyEvent(downKeyDown); + assertTrue(didConsume); + assertEquals(Selection.getSelectionStart(editable), 19); + + // Emoji Modifier + didConsume = adaptor.sendKeyEvent(downKeyDown); + assertTrue(didConsume); + assertEquals(Selection.getSelectionStart(editable), 16); + + // Emoji Modifier with invalid base + adaptor.setSelection(14, 14); + didConsume = adaptor.sendKeyEvent(downKeyDown); + assertTrue(didConsume); + assertEquals(Selection.getSelectionStart(editable), 13); + adaptor.setSelection(14, 14); + didConsume = adaptor.sendKeyEvent(downKeyDown); + assertTrue(didConsume); + assertEquals(Selection.getSelectionStart(editable), 13); + + // Line Feed + adaptor.setSelection(12, 12); + didConsume = adaptor.sendKeyEvent(downKeyDown); + assertTrue(didConsume); + assertEquals(Selection.getSelectionStart(editable), 11); + + // Carriage Return + adaptor.setSelection(12, 12); + didConsume = adaptor.sendKeyEvent(downKeyDown); + assertTrue(didConsume); + assertEquals(Selection.getSelectionStart(editable), 11); + + // Carriage Return and Line Feed + didConsume = adaptor.sendKeyEvent(downKeyDown); + assertTrue(didConsume); + assertEquals(Selection.getSelectionStart(editable), 9); + + // Regional Indicator Symbol odd + didConsume = adaptor.sendKeyEvent(downKeyDown); + assertTrue(didConsume); + assertEquals(Selection.getSelectionStart(editable), 7); + + // Regional Indicator Symbol even + didConsume = adaptor.sendKeyEvent(downKeyDown); + assertTrue(didConsume); + assertEquals(Selection.getSelectionStart(editable), 3); + + // Simple Emoji + didConsume = adaptor.sendKeyEvent(downKeyDown); + assertTrue(didConsume); + assertEquals(Selection.getSelectionStart(editable), 1); + + // First CodePoint + didConsume = adaptor.sendKeyEvent(downKeyDown); + assertTrue(didConsume); + assertEquals(Selection.getSelectionStart(editable), 0); } @Test