Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 25e455e6 authored by Justin Ghan's avatar Justin Ghan Committed by Android (Google) Code Review
Browse files

Merge "TextView implementation of remove space gesture, and join or split gesture"

parents 50798797 52b915f4
Loading
Loading
Loading
Loading
+139 −16
Original line number Diff line number Diff line
@@ -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;
    }
+6 −0
Original line number Diff line number Diff line
@@ -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;
        }