From 1ce033402e8b0894e8663bf3e193bc1d66425fff Mon Sep 17 00:00:00 2001 From: Justin Ghan Date: Tue, 11 Oct 2022 00:22:09 -0700 Subject: [PATCH] TextView implementation of select range and delete range gestures Bug: 247570808 Test: atest android.widget.cts.TextViewHandwritingGestureTest Change-Id: I2cfd579c1dd213fa1847f302b56d22bda0c96f19 --- .../android/view/inputmethod/EditorInfo.java | 8 + core/java/android/widget/TextView.java | 161 ++++++++++++------ .../inputmethod/EditableInputConnection.java | 6 + 3 files changed, 126 insertions(+), 49 deletions(-) diff --git a/core/java/android/view/inputmethod/EditorInfo.java b/core/java/android/view/inputmethod/EditorInfo.java index 4a79ba62de696..febdac26a0f22 100644 --- a/core/java/android/view/inputmethod/EditorInfo.java +++ b/core/java/android/view/inputmethod/EditorInfo.java @@ -595,6 +595,10 @@ public class EditorInfo implements InputType, Parcelable { == HandwritingGesture.GESTURE_TYPE_SELECT) { list.add(SelectGesture.class); } + if ((mSupportedHandwritingGestureTypes & HandwritingGesture.GESTURE_TYPE_SELECT_RANGE) + == HandwritingGesture.GESTURE_TYPE_SELECT_RANGE) { + list.add(SelectRangeGesture.class); + } if ((mSupportedHandwritingGestureTypes & HandwritingGesture.GESTURE_TYPE_INSERT) == HandwritingGesture.GESTURE_TYPE_INSERT) { list.add(InsertGesture.class); @@ -603,6 +607,10 @@ public class EditorInfo implements InputType, Parcelable { == HandwritingGesture.GESTURE_TYPE_DELETE) { list.add(DeleteGesture.class); } + if ((mSupportedHandwritingGestureTypes & HandwritingGesture.GESTURE_TYPE_DELETE_RANGE) + == HandwritingGesture.GESTURE_TYPE_DELETE_RANGE) { + list.add(DeleteRangeGesture.class); + } if ((mSupportedHandwritingGestureTypes & HandwritingGesture.GESTURE_TYPE_REMOVE_SPACE) == HandwritingGesture.GESTURE_TYPE_REMOVE_SPACE) { list.add(RemoveSpaceGesture.class); diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index a2e9faa81eeb2..57103e4955ca5 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -189,6 +189,7 @@ import android.view.inputmethod.CompletionInfo; import android.view.inputmethod.CorrectionInfo; import android.view.inputmethod.CursorAnchorInfo; import android.view.inputmethod.DeleteGesture; +import android.view.inputmethod.DeleteRangeGesture; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.ExtractedText; import android.view.inputmethod.ExtractedTextRequest; @@ -199,6 +200,7 @@ import android.view.inputmethod.InsertGesture; import android.view.inputmethod.JoinOrSplitGesture; import android.view.inputmethod.RemoveSpaceGesture; import android.view.inputmethod.SelectGesture; +import android.view.inputmethod.SelectRangeGesture; import android.view.inspector.InspectableProperty; import android.view.inspector.InspectableProperty.EnumEntry; import android.view.inspector.InspectableProperty.FlagEntry; @@ -9096,7 +9098,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener ArrayList> gestures = new ArrayList<>(); gestures.add(SelectGesture.class); + gestures.add(SelectRangeGesture.class); gestures.add(DeleteGesture.class); + gestures.add(DeleteRangeGesture.class); gestures.add(InsertGesture.class); gestures.add(RemoveSpaceGesture.class); gestures.add(JoinOrSplitGesture.class); @@ -9324,6 +9328,26 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return InputConnection.HANDWRITING_GESTURE_RESULT_SUCCESS; } + /** @hide */ + public int performHandwritingSelectRangeGesture(@NonNull SelectRangeGesture gesture) { + Range startRange = getRangeForRect( + convertFromScreenToContentCoordinates(gesture.getSelectionStartArea()), + gesture.getGranularity()); + if (startRange == null) { + return handleGestureFailure(gesture); + } + Range endRange = getRangeForRect( + convertFromScreenToContentCoordinates(gesture.getSelectionEndArea()), + gesture.getGranularity()); + if (endRange == null || endRange.getUpper() <= startRange.getLower()) { + return handleGestureFailure(gesture); + } + Range range = startRange.extend(endRange); + Selection.setSelection(getEditableText(), range.getLower(), range.getUpper()); + mEditor.startSelectionActionModeAsync(/* adjustSelection= */ false); + return InputConnection.HANDWRITING_GESTURE_RESULT_SUCCESS; + } + /** @hide */ public int performHandwritingDeleteGesture(@NonNull DeleteGesture gesture) { Range range = getRangeForRect( @@ -9332,60 +9356,99 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (range == null) { return handleGestureFailure(gesture); } + + if (gesture.getGranularity() == HandwritingGesture.GRANULARITY_WORD) { + range = adjustHandwritingDeleteGestureRange(range); + } + + getEditableText().delete(range.getLower(), range.getUpper()); + Selection.setSelection(getEditableText(), range.getLower()); + return InputConnection.HANDWRITING_GESTURE_RESULT_SUCCESS; + } + + /** @hide */ + public int performHandwritingDeleteRangeGesture(@NonNull DeleteRangeGesture gesture) { + Range startRange = getRangeForRect( + convertFromScreenToContentCoordinates(gesture.getDeletionStartArea()), + gesture.getGranularity()); + if (startRange == null) { + return handleGestureFailure(gesture); + } + Range endRange = getRangeForRect( + convertFromScreenToContentCoordinates(gesture.getDeletionEndArea()), + gesture.getGranularity()); + if (endRange == null) { + return handleGestureFailure(gesture); + } + Range range = startRange.extend(endRange); + + if (gesture.getGranularity() == HandwritingGesture.GRANULARITY_WORD) { + range = adjustHandwritingDeleteGestureRange(range); + } + + getEditableText().delete(range.getLower(), range.getUpper()); + Selection.setSelection(getEditableText(), range.getLower()); + return InputConnection.HANDWRITING_GESTURE_RESULT_SUCCESS; + } + + private Range adjustHandwritingDeleteGestureRange(Range range) { + // For handwriting delete gestures with word granularity, adjust the start and end offsets + // to remove extra whitespace around the deleted text. + int start = range.getLower(); int end = range.getUpper(); - // For word granularity, adjust the start and end offsets to remove extra whitespace around - // the deleted text. - if (gesture.getGranularity() == HandwritingGesture.GRANULARITY_WORD) { - // If the deleted text is at the start of the text, the behavior is the same as the case - // where the deleted text follows a new line character. - int codePointBeforeStart = start > 0 - ? Character.codePointBefore(mText, start) : TextUtils.LINE_FEED_CODE_POINT; - // If the deleted text is at the end of the text, the behavior is the same as the case - // where the deleted text precedes a new line character. - int codePointAtEnd = end < mText.length() - ? Character.codePointAt(mText, end) : TextUtils.LINE_FEED_CODE_POINT; - if (TextUtils.isWhitespaceExceptNewline(codePointBeforeStart) - && (TextUtils.isWhitespace(codePointAtEnd) - || TextUtils.isPunctuation(codePointAtEnd))) { - // Remove whitespace (except new lines) before the deleted text, in these cases: - // - There is whitespace following the deleted text - // e.g. "one [deleted] three" -> "one | three" -> "one| three" - // - There is punctuation following the deleted text - // e.g. "one [deleted]!" -> "one |!" -> "one|!" - // - There is a new line following the deleted text - // e.g. "one [deleted]\n" -> "one |\n" -> "one|\n" - // - The deleted text is at the end of the text - // e.g. "one [deleted]" -> "one |" -> "one|" - // (The pipe | indicates the cursor position.) - do { - start -= Character.charCount(codePointBeforeStart); - if (start == 0) break; - codePointBeforeStart = Character.codePointBefore(mText, start); - } while (TextUtils.isWhitespaceExceptNewline(codePointBeforeStart)); - } else if (TextUtils.isWhitespaceExceptNewline(codePointAtEnd) - && (TextUtils.isWhitespace(codePointBeforeStart) - || TextUtils.isPunctuation(codePointBeforeStart))) { - // Remove whitespace (except new lines) after the deleted text, in these cases: - // - There is punctuation preceding the deleted text - // e.g. "([deleted] two)" -> "(| two)" -> "(|two)" - // - There is a new line preceding the deleted text - // e.g. "\n[deleted] two" -> "\n| two" -> "\n|two" - // - The deleted text is at the start of the text - // e.g. "[deleted] two" -> "| two" -> "|two" - // (The pipe | indicates the cursor position.) - do { - end += Character.charCount(codePointAtEnd); - if (end == mText.length()) break; - codePointAtEnd = Character.codePointAt(mText, end); - } while (TextUtils.isWhitespaceExceptNewline(codePointAtEnd)); - } + // If the deleted text is at the start of the text, the behavior is the same as the case + // where the deleted text follows a new line character. + int codePointBeforeStart = start > 0 + ? Character.codePointBefore(mText, start) : TextUtils.LINE_FEED_CODE_POINT; + // If the deleted text is at the end of the text, the behavior is the same as the case where + // the deleted text precedes a new line character. + int codePointAtEnd = end < mText.length() + ? Character.codePointAt(mText, end) : TextUtils.LINE_FEED_CODE_POINT; + + if (TextUtils.isWhitespaceExceptNewline(codePointBeforeStart) + && (TextUtils.isWhitespace(codePointAtEnd) + || TextUtils.isPunctuation(codePointAtEnd))) { + // Remove whitespace (except new lines) before the deleted text, in these cases: + // - There is whitespace following the deleted text + // e.g. "one [deleted] three" -> "one | three" -> "one| three" + // - There is punctuation following the deleted text + // e.g. "one [deleted]!" -> "one |!" -> "one|!" + // - There is a new line following the deleted text + // e.g. "one [deleted]\n" -> "one |\n" -> "one|\n" + // - The deleted text is at the end of the text + // e.g. "one [deleted]" -> "one |" -> "one|" + // (The pipe | indicates the cursor position.) + do { + start -= Character.charCount(codePointBeforeStart); + if (start == 0) break; + codePointBeforeStart = Character.codePointBefore(mText, start); + } while (TextUtils.isWhitespaceExceptNewline(codePointBeforeStart)); + return new Range(start, end); } - getEditableText().delete(start, end); - Selection.setSelection(getEditableText(), start); - return InputConnection.HANDWRITING_GESTURE_RESULT_SUCCESS; + if (TextUtils.isWhitespaceExceptNewline(codePointAtEnd) + && (TextUtils.isWhitespace(codePointBeforeStart) + || TextUtils.isPunctuation(codePointBeforeStart))) { + // Remove whitespace (except new lines) after the deleted text, in these cases: + // - There is punctuation preceding the deleted text + // e.g. "([deleted] two)" -> "(| two)" -> "(|two)" + // - There is a new line preceding the deleted text + // e.g. "\n[deleted] two" -> "\n| two" -> "\n|two" + // - The deleted text is at the start of the text + // e.g. "[deleted] two" -> "| two" -> "|two" + // (The pipe | indicates the cursor position.) + do { + end += Character.charCount(codePointAtEnd); + if (end == mText.length()) break; + codePointAtEnd = Character.codePointAt(mText, end); + } while (TextUtils.isWhitespaceExceptNewline(codePointAtEnd)); + return new Range(start, end); + } + + // Return the original range. + return range; } /** @hide */ diff --git a/core/java/com/android/internal/inputmethod/EditableInputConnection.java b/core/java/com/android/internal/inputmethod/EditableInputConnection.java index f260d7dfc6a63..f600c36cd8c91 100644 --- a/core/java/com/android/internal/inputmethod/EditableInputConnection.java +++ b/core/java/com/android/internal/inputmethod/EditableInputConnection.java @@ -35,6 +35,7 @@ import android.view.inputmethod.BaseInputConnection; import android.view.inputmethod.CompletionInfo; import android.view.inputmethod.CorrectionInfo; import android.view.inputmethod.DeleteGesture; +import android.view.inputmethod.DeleteRangeGesture; import android.view.inputmethod.DumpableInputConnection; import android.view.inputmethod.ExtractedText; import android.view.inputmethod.ExtractedTextRequest; @@ -44,6 +45,7 @@ import android.view.inputmethod.InsertGesture; import android.view.inputmethod.JoinOrSplitGesture; import android.view.inputmethod.RemoveSpaceGesture; import android.view.inputmethod.SelectGesture; +import android.view.inputmethod.SelectRangeGesture; import android.widget.TextView; import java.util.concurrent.Executor; @@ -275,8 +277,12 @@ public final class EditableInputConnection extends BaseInputConnection int result; if (gesture instanceof SelectGesture) { result = mTextView.performHandwritingSelectGesture((SelectGesture) gesture); + } else if (gesture instanceof SelectRangeGesture) { + result = mTextView.performHandwritingSelectRangeGesture((SelectRangeGesture) gesture); } else if (gesture instanceof DeleteGesture) { result = mTextView.performHandwritingDeleteGesture((DeleteGesture) gesture); + } else if (gesture instanceof DeleteRangeGesture) { + result = mTextView.performHandwritingDeleteRangeGesture((DeleteRangeGesture) gesture); } else if (gesture instanceof InsertGesture) { result = mTextView.performHandwritingInsertGesture((InsertGesture) gesture); } else if (gesture instanceof RemoveSpaceGesture) {