From 7463aff039c0270294d1d0b26545014366e9f93d Mon Sep 17 00:00:00 2001 From: Ali Mahdiyar Date: Mon, 30 Mar 2020 01:05:30 +0430 Subject: [PATCH 1/4] Fix bug in handling delete key event for android --- .../editing/InputConnectionAdaptor.java | 46 +++++++++++++------ 1 file changed, 32 insertions(+), 14 deletions(-) diff --git a/shell/platform/android/io/flutter/plugin/editing/InputConnectionAdaptor.java b/shell/platform/android/io/flutter/plugin/editing/InputConnectionAdaptor.java index d1b58039fb910..8e5521106940d 100644 --- a/shell/platform/android/io/flutter/plugin/editing/InputConnectionAdaptor.java +++ b/shell/platform/android/io/flutter/plugin/editing/InputConnectionAdaptor.java @@ -325,21 +325,39 @@ public boolean sendKeyEvent(KeyEvent event) { // TODO(garyq): Explore how to obtain per-character direction. The // isRTLCharAt() call below is returning blanket direction assumption // based on the first character in the line. - boolean isRtl = mLayout.isRtlCharAt(mLayout.getLineForOffset(selStart)); - try { - if (isRtl) { - Selection.extendRight(mEditable, mLayout); - } else { - Selection.extendLeft(mEditable, mLayout); + + Boolean selectedCharIsEmoji = false; + if (selStart > 1) { + final String emo_regex = + "([\\u20a0-\\u32ff\\ud83c\\udc00-\\ud83d\\udeff\\udbb9\\udce5-\\udbb9\\udcee])"; + String lastChars = mLayout.getText().subSequence(selStart - 2, selStart).toString(); + selectedCharIsEmoji = lastChars.matches(emo_regex); + } + + // The Selection.extendLeft and extendRight calls have some problems + // when mixing RTL and LTR text so if selected character is not emoji + // simply select that character + if (selectedCharIsEmoji) { + boolean isRtl = + mLayout.isRtlCharAt(mLayout.getLineStart(mLayout.getLineForOffset(selStart))); + Log.i("isRtl", Boolean.toString(isRtl)); + try { + if (isRtl) { + Selection.extendRight(mEditable, mLayout); + } else { + Selection.extendLeft(mEditable, mLayout); + } + } catch (IndexOutOfBoundsException e) { + // On some Chinese devices (primarily Huawei, some Xiaomi), + // on initial app startup before focus is lost, the + // Selection.extendLeft and extendRight calls always extend + // from the index of the initial contents of mEditable. This + // try-catch will prevent crashing on Huawei devices by falling + // back to a simple way of deletion, although this a hack and + // will not handle emojis. + Selection.setSelection(mEditable, selStart, selStart - 1); } - } catch (IndexOutOfBoundsException e) { - // On some Chinese devices (primarily Huawei, some Xiaomi), - // on initial app startup before focus is lost, the - // Selection.extendLeft and extendRight calls always extend - // from the index of the initial contents of mEditable. This - // try-catch will prevent crashing on Huawei devices by falling - // back to a simple way of deletion, although this a hack and - // will not handle emojis. + } else { Selection.setSelection(mEditable, selStart, selStart - 1); } int newStart = clampIndexToEditable(Selection.getSelectionStart(mEditable), mEditable); From 98c14b1b29b77c8de2111dadb643b5a0cd7653da Mon Sep 17 00:00:00 2001 From: Ali Mahdiyar Date: Thu, 2 Apr 2020 19:03:07 +0430 Subject: [PATCH 2/4] use android QwertyKeyListener for deleting backward --- .../editing/InputConnectionAdaptor.java | 53 +++---------------- .../editing/InputConnectionAdaptorTest.java | 29 ++++++++++ 2 files changed, 36 insertions(+), 46 deletions(-) diff --git a/shell/platform/android/io/flutter/plugin/editing/InputConnectionAdaptor.java b/shell/platform/android/io/flutter/plugin/editing/InputConnectionAdaptor.java index 8e5521106940d..1c90492f6ad18 100644 --- a/shell/platform/android/io/flutter/plugin/editing/InputConnectionAdaptor.java +++ b/shell/platform/android/io/flutter/plugin/editing/InputConnectionAdaptor.java @@ -16,6 +16,8 @@ import android.text.Layout; import android.text.Selection; import android.text.TextPaint; +import android.text.method.QwertyKeyListener; +import android.text.method.TextKeyListener.Capitalize; import android.view.KeyEvent; import android.view.View; import android.view.inputmethod.BaseInputConnection; @@ -321,53 +323,12 @@ public boolean sendKeyEvent(KeyEvent event) { updateEditingState(); return true; } else if (selStart > 0) { - // Delete to the left/right of the cursor depending on direction of text. - // TODO(garyq): Explore how to obtain per-character direction. The - // isRTLCharAt() call below is returning blanket direction assumption - // based on the first character in the line. - - Boolean selectedCharIsEmoji = false; - if (selStart > 1) { - final String emo_regex = - "([\\u20a0-\\u32ff\\ud83c\\udc00-\\ud83d\\udeff\\udbb9\\udce5-\\udbb9\\udcee])"; - String lastChars = mLayout.getText().subSequence(selStart - 2, selStart).toString(); - selectedCharIsEmoji = lastChars.matches(emo_regex); + QwertyKeyListener qwertyKeyListener = new QwertyKeyListener(Capitalize.NONE, false); + if (qwertyKeyListener.onKeyDown(null, mEditable, event.getKeyCode(), event)) { + updateEditingState(); + return true; } - - // The Selection.extendLeft and extendRight calls have some problems - // when mixing RTL and LTR text so if selected character is not emoji - // simply select that character - if (selectedCharIsEmoji) { - boolean isRtl = - mLayout.isRtlCharAt(mLayout.getLineStart(mLayout.getLineForOffset(selStart))); - Log.i("isRtl", Boolean.toString(isRtl)); - try { - if (isRtl) { - Selection.extendRight(mEditable, mLayout); - } else { - Selection.extendLeft(mEditable, mLayout); - } - } catch (IndexOutOfBoundsException e) { - // On some Chinese devices (primarily Huawei, some Xiaomi), - // on initial app startup before focus is lost, the - // Selection.extendLeft and extendRight calls always extend - // from the index of the initial contents of mEditable. This - // try-catch will prevent crashing on Huawei devices by falling - // back to a simple way of deletion, although this a hack and - // will not handle emojis. - Selection.setSelection(mEditable, selStart, selStart - 1); - } - } else { - Selection.setSelection(mEditable, selStart, selStart - 1); - } - int newStart = clampIndexToEditable(Selection.getSelectionStart(mEditable), mEditable); - int newEnd = clampIndexToEditable(Selection.getSelectionEnd(mEditable), mEditable); - Selection.setSelection(mEditable, Math.min(newStart, newEnd)); - // Min/Max the values since RTL selections will start at a higher - // index than they end at. - mEditable.delete(Math.min(newStart, newEnd), Math.max(newStart, newEnd)); - updateEditingState(); - return true; + return false; } } else if (event.getKeyCode() == KeyEvent.KEYCODE_DPAD_LEFT) { int selStart = Selection.getSelectionStart(mEditable); 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 bf0f513a0835e..bbca2464a6232 100644 --- a/shell/platform/android/test/io/flutter/plugin/editing/InputConnectionAdaptorTest.java +++ b/shell/platform/android/test/io/flutter/plugin/editing/InputConnectionAdaptorTest.java @@ -313,15 +313,44 @@ public void inputConnectionAdaptor_RepeatFilter() throws NullPointerException { assertEquals(textInputChannel.selectionEnd, 4); } + @Test + public void testSendKeyEvent_delKeyDeletesBackward() { + int selStart = 29; + Editable editable = sampleRtlEditable(selStart, selStart); + InputConnectionAdaptor adaptor = sampleInputConnectionAdaptor(editable); + + KeyEvent downKeyDown = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL); + + for (int i = 0; i < 9; i++) { + boolean didConsume = adaptor.sendKeyEvent(downKeyDown); + 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); + } + private static final String SAMPLE_TEXT = "Lorem ipsum dolor sit amet," + "\nconsectetur adipiscing elit."; + private static final String SAMPLE_RTL_TEXT = "متن ساختگی" + "\nبرای تستfor test😊"; + private static Editable sampleEditable(int selStart, int selEnd) { SpannableStringBuilder sample = new SpannableStringBuilder(SAMPLE_TEXT); Selection.setSelection(sample, selStart, selEnd); return sample; } + private static Editable sampleRtlEditable(int selStart, int selEnd) { + SpannableStringBuilder sample = new SpannableStringBuilder(SAMPLE_RTL_TEXT); + Selection.setSelection(sample, selStart, selEnd); + return sample; + } + private static InputConnectionAdaptor sampleInputConnectionAdaptor(Editable editable) { View testView = new View(RuntimeEnvironment.application); int client = 0; From caff0921c822dad82370c122b7636fe431289d05 Mon Sep 17 00:00:00 2001 From: Ali Mahdiyar Date: Tue, 7 Apr 2020 16:17:20 +0430 Subject: [PATCH 3/4] use TextKeyListener instead of QwertyKeyListener --- .../io/flutter/plugin/editing/InputConnectionAdaptor.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/shell/platform/android/io/flutter/plugin/editing/InputConnectionAdaptor.java b/shell/platform/android/io/flutter/plugin/editing/InputConnectionAdaptor.java index 1c90492f6ad18..416f3fa295c18 100644 --- a/shell/platform/android/io/flutter/plugin/editing/InputConnectionAdaptor.java +++ b/shell/platform/android/io/flutter/plugin/editing/InputConnectionAdaptor.java @@ -16,7 +16,7 @@ import android.text.Layout; import android.text.Selection; import android.text.TextPaint; -import android.text.method.QwertyKeyListener; +import android.text.method.TextKeyListener; import android.text.method.TextKeyListener.Capitalize; import android.view.KeyEvent; import android.view.View; @@ -323,8 +323,8 @@ public boolean sendKeyEvent(KeyEvent event) { updateEditingState(); return true; } else if (selStart > 0) { - QwertyKeyListener qwertyKeyListener = new QwertyKeyListener(Capitalize.NONE, false); - if (qwertyKeyListener.onKeyDown(null, mEditable, event.getKeyCode(), event)) { + TextKeyListener textKeyListener = new TextKeyListener(Capitalize.NONE, false); + if (textKeyListener.onKeyDown(null, mEditable, event.getKeyCode(), event)) { updateEditingState(); return true; } From 46be3c6cdb0ac20482fe9d0c2dcc02486cac67c5 Mon Sep 17 00:00:00 2001 From: Ali Mahdiyar Date: Tue, 7 Apr 2020 16:30:54 +0430 Subject: [PATCH 4/4] use TextKeyListener getInstance instead of manual instantiating --- .../io/flutter/plugin/editing/InputConnectionAdaptor.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/shell/platform/android/io/flutter/plugin/editing/InputConnectionAdaptor.java b/shell/platform/android/io/flutter/plugin/editing/InputConnectionAdaptor.java index 416f3fa295c18..a990ff386108e 100644 --- a/shell/platform/android/io/flutter/plugin/editing/InputConnectionAdaptor.java +++ b/shell/platform/android/io/flutter/plugin/editing/InputConnectionAdaptor.java @@ -17,7 +17,6 @@ import android.text.Selection; import android.text.TextPaint; import android.text.method.TextKeyListener; -import android.text.method.TextKeyListener.Capitalize; import android.view.KeyEvent; import android.view.View; import android.view.inputmethod.BaseInputConnection; @@ -323,8 +322,7 @@ public boolean sendKeyEvent(KeyEvent event) { updateEditingState(); return true; } else if (selStart > 0) { - TextKeyListener textKeyListener = new TextKeyListener(Capitalize.NONE, false); - if (textKeyListener.onKeyDown(null, mEditable, event.getKeyCode(), event)) { + if (TextKeyListener.getInstance().onKeyDown(null, mEditable, event.getKeyCode(), event)) { updateEditingState(); return true; }