Loading core/java/android/widget/TextView.java +139 −16 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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. Loading Loading @@ -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). Loading Loading @@ -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; } Loading Loading @@ -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); } Loading @@ -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); } Loading @@ -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)) { Loading @@ -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() Loading @@ -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(); Loading @@ -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 Loading Loading @@ -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; } Loading core/java/com/android/internal/inputmethod/EditableInputConnection.java +6 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; } Loading Loading
core/java/android/widget/TextView.java +139 −16 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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. Loading Loading @@ -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). Loading Loading @@ -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; } Loading Loading @@ -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); } Loading @@ -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); } Loading @@ -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)) { Loading @@ -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() Loading @@ -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(); Loading @@ -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 Loading Loading @@ -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; } Loading
core/java/com/android/internal/inputmethod/EditableInputConnection.java +6 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; } Loading