diff options
| -rw-r--r-- | core/java/android/widget/TextView.java | 155 | ||||
| -rw-r--r-- | core/java/com/android/internal/inputmethod/EditableInputConnection.java | 6 |
2 files changed, 145 insertions, 16 deletions
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 68b902f14079..41d00a267c59 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -195,6 +195,8 @@ import android.view.inputmethod.HandwritingGesture; import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputMethodManager; import android.view.inputmethod.InsertGesture; +import android.view.inputmethod.JoinOrSplitGesture; +import android.view.inputmethod.RemoveSpaceGesture; import android.view.inputmethod.SelectGesture; import android.view.inspector.InspectableProperty; import android.view.inspector.InspectableProperty.EnumEntry; @@ -238,6 +240,8 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; import java.util.function.Supplier; +import java.util.regex.Matcher; +import java.util.regex.Pattern; /** * A user interface element that displays text to the user. @@ -1033,6 +1037,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener // // End of autofill-related attributes + private Pattern mWhitespacePattern; + /** * Kick-start the font cache for the zygote process (to pay the cost of * initializing freetype for our default font only once). @@ -9090,6 +9096,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener gestures.add(SelectGesture.class); gestures.add(DeleteGesture.class); gestures.add(InsertGesture.class); + gestures.add(RemoveSpaceGesture.class); + gestures.add(JoinOrSplitGesture.class); outAttrs.setSupportedHandwritingGestures(gestures); return ic; } @@ -9303,7 +9311,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener /** @hide */ public int performHandwritingSelectGesture(@NonNull SelectGesture gesture) { - int[] range = getRangeForRect(gesture.getSelectionArea(), gesture.getGranularity()); + int[] range = getRangeForRect( + convertFromScreenToContentCoordinates(gesture.getSelectionArea()), + gesture.getGranularity()); if (range == null) { return handleGestureFailure(gesture); } @@ -9314,7 +9324,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener /** @hide */ public int performHandwritingDeleteGesture(@NonNull DeleteGesture gesture) { - int[] range = getRangeForRect(gesture.getDeletionArea(), gesture.getGranularity()); + int[] range = getRangeForRect( + convertFromScreenToContentCoordinates(gesture.getDeletionArea()), + gesture.getGranularity()); if (range == null) { return handleGestureFailure(gesture); } @@ -9326,13 +9338,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener /** @hide */ public int performHandwritingInsertGesture(@NonNull InsertGesture gesture) { - PointF point = gesture.getInsertionPoint(); - // The coordinates provided are screen coordinates - transform to content coordinates. - int[] screenToViewport = getLocationOnScreen(); - point.offset( - -(screenToViewport[0] + viewportToContentHorizontalOffset()), - -(screenToViewport[1] + viewportToContentVerticalOffset())); - + PointF point = convertFromScreenToContentCoordinates(gesture.getInsertionPoint()); int line = mLayout.getLineForVertical((int) point.y); if (point.y < mLayout.getLineTop(line) || point.y > mLayout.getLineBottomWithoutSpacing(line)) { @@ -9349,6 +9355,105 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return InputConnection.HANDWRITING_GESTURE_RESULT_SUCCESS; } + /** @hide */ + public int performHandwritingRemoveSpaceGesture(@NonNull RemoveSpaceGesture gesture) { + PointF startPoint = convertFromScreenToContentCoordinates(gesture.getStartPoint()); + PointF endPoint = convertFromScreenToContentCoordinates(gesture.getEndPoint()); + + // The operation should be applied to the first line of text touched by the line joining + // the points. + int yMin = (int) Math.min(startPoint.y, endPoint.y); + int yMax = (int) Math.max(startPoint.y, endPoint.y); + int line = mLayout.getLineForVertical(yMin); + if (yMax < mLayout.getLineTop(line)) { + // Both points are above the top of the first line. + return handleGestureFailure(gesture); + } + if (yMin > mLayout.getLineBottomWithoutSpacing(line)) { + if (line == mLayout.getLineCount() - 1 || yMax < mLayout.getLineTop(line + 1)) { + // The points are below the last line, or they are between two lines. + return handleGestureFailure(gesture); + } else { + // Apply the operation to the next line. + line++; + } + } + if (Math.max(startPoint.x, endPoint.x) < mLayout.getLineLeft(line) + || Math.min(startPoint.x, endPoint.x) > mLayout.getLineRight(line)) { + return handleGestureFailure(gesture); + } + + // The operation should be applied to all characters touched by the line joining the points. + int startOffset = mLayout.getOffsetForHorizontal(line, startPoint.x); + int endOffset = mLayout.getOffsetForHorizontal(line, endPoint.x); + if (startOffset == endOffset) { + return handleGestureFailure(gesture); + } else if (startOffset > endOffset) { + int tmp = startOffset; + startOffset = endOffset; + endOffset = tmp; + } + // TODO(b/247557062): The boundary offsets might be off by one. We should check which side + // of the offset the point is on, and adjust if necessary. + // TODO(b/247557062): This doesn't handle bidirectional text correctly. + + Pattern whitespacePattern = getWhitespacePattern(); + Matcher matcher = whitespacePattern.matcher(mText.subSequence(startOffset, endOffset)); + int lastRemoveOffset = -1; + while (matcher.find()) { + lastRemoveOffset = startOffset + matcher.start(); + getEditableText().delete(lastRemoveOffset, startOffset + matcher.end()); + startOffset = lastRemoveOffset; + endOffset -= matcher.end() - matcher.start(); + if (startOffset == endOffset) { + break; + } + matcher = whitespacePattern.matcher(mText.subSequence(startOffset, endOffset)); + } + if (lastRemoveOffset == -1) { + return handleGestureFailure(gesture); + } + Selection.setSelection(getEditableText(), lastRemoveOffset); + return InputConnection.HANDWRITING_GESTURE_RESULT_SUCCESS; + } + + /** @hide */ + public int performHandwritingJoinOrSplitGesture(@NonNull JoinOrSplitGesture gesture) { + PointF point = convertFromScreenToContentCoordinates(gesture.getJoinOrSplitPoint()); + + int line = mLayout.getLineForVertical((int) point.y); + if (point.y < mLayout.getLineTop(line) + || point.y > mLayout.getLineBottomWithoutSpacing(line)) { + return handleGestureFailure(gesture); + } + if (point.x < mLayout.getLineLeft(line) || point.x > mLayout.getLineRight(line)) { + return handleGestureFailure(gesture); + } + + int startOffset = mLayout.getOffsetForHorizontal(line, point.x); + if (mLayout.isLevelBoundary(startOffset)) { + // TODO(b/247551937): Support gesture at level boundaries. + return handleGestureFailure(gesture); + } + + int endOffset = startOffset; + while (startOffset > 0 && Character.isWhitespace(mText.charAt(startOffset - 1))) { + startOffset--; + } + while (endOffset < mText.length() && Character.isWhitespace(mText.charAt(endOffset))) { + endOffset++; + } + if (startOffset < endOffset) { + getEditableText().delete(startOffset, endOffset); + Selection.setSelection(getEditableText(), startOffset); + } else { + // No whitespace found, so insert a space. + getEditableText().insert(startOffset, " "); + Selection.setSelection(getEditableText(), startOffset + 1); + } + return InputConnection.HANDWRITING_GESTURE_RESULT_SUCCESS; + } + private int handleGestureFailure(HandwritingGesture gesture) { if (!TextUtils.isEmpty(gesture.getFallbackText())) { getEditableText() @@ -9360,13 +9465,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener @Nullable private int[] getRangeForRect(@NonNull RectF area, int granularity) { - // The coordinates provided are screen coordinates - transform to content coordinates. - int[] screenToViewport = getLocationOnScreen(); - area = new RectF(area); - area.offset( - -(screenToViewport[0] + viewportToContentHorizontalOffset()), - -(screenToViewport[1] + viewportToContentVerticalOffset())); - SegmentIterator segmentIterator; if (granularity == HandwritingGesture.GRANULARITY_WORD) { WordIterator wordIterator = getWordIterator(); @@ -9379,6 +9477,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return mLayout.getRangeForRect(area, segmentIterator); } + private Pattern getWhitespacePattern() { + if (mWhitespacePattern == null) { + mWhitespacePattern = Pattern.compile("\\s+"); + } + return mWhitespacePattern; + } + /** @hide */ @VisibleForTesting @UnsupportedAppUsage @@ -10631,6 +10736,24 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener r.bottom += verticalOffset; } + private PointF convertFromScreenToContentCoordinates(PointF point) { + int[] screenToViewport = getLocationOnScreen(); + PointF copy = new PointF(point); + copy.offset( + -(screenToViewport[0] + viewportToContentHorizontalOffset()), + -(screenToViewport[1] + viewportToContentVerticalOffset())); + return copy; + } + + private RectF convertFromScreenToContentCoordinates(RectF rect) { + int[] screenToViewport = getLocationOnScreen(); + RectF copy = new RectF(rect); + copy.offset( + -(screenToViewport[0] + viewportToContentHorizontalOffset()), + -(screenToViewport[1] + viewportToContentVerticalOffset())); + return copy; + } + int viewportToContentHorizontalOffset() { return getCompoundPaddingLeft() - mScrollX; } diff --git a/core/java/com/android/internal/inputmethod/EditableInputConnection.java b/core/java/com/android/internal/inputmethod/EditableInputConnection.java index 1e5208739314..f260d7dfc6a6 100644 --- a/core/java/com/android/internal/inputmethod/EditableInputConnection.java +++ b/core/java/com/android/internal/inputmethod/EditableInputConnection.java @@ -41,6 +41,8 @@ import android.view.inputmethod.ExtractedTextRequest; import android.view.inputmethod.HandwritingGesture; import android.view.inputmethod.InputConnection; import android.view.inputmethod.InsertGesture; +import android.view.inputmethod.JoinOrSplitGesture; +import android.view.inputmethod.RemoveSpaceGesture; import android.view.inputmethod.SelectGesture; import android.widget.TextView; @@ -277,6 +279,10 @@ public final class EditableInputConnection extends BaseInputConnection result = mTextView.performHandwritingDeleteGesture((DeleteGesture) gesture); } else if (gesture instanceof InsertGesture) { result = mTextView.performHandwritingInsertGesture((InsertGesture) gesture); + } else if (gesture instanceof RemoveSpaceGesture) { + result = mTextView.performHandwritingRemoveSpaceGesture((RemoveSpaceGesture) gesture); + } else if (gesture instanceof JoinOrSplitGesture) { + result = mTextView.performHandwritingJoinOrSplitGesture((JoinOrSplitGesture) gesture); } else { result = HANDWRITING_GESTURE_RESULT_UNSUPPORTED; } |