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

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

Merge changes from topics "gesture-preview-highlight", "gesture-preview-impl"

* changes:
  TextView implementation of select and delete gesture previews
  Text highlighting for select and delete gesture previews
parents 6de6de36 691cbb96
Loading
Loading
Loading
Loading
+3 −2
Original line number Diff line number Diff line
@@ -2082,7 +2082,8 @@ public class Editor {
        }

        if (selectionHighlight != null && selectionStart == selectionEnd
                && mDrawableForCursor != null) {
                && mDrawableForCursor != null
                && !mTextView.hasGesturePreviewHighlight()) {
            drawCursor(canvas, cursorOffsetVertical);
            // Rely on the drawable entirely, do not draw the cursor line.
            // Has to be done after the IMM related code above which relies on the highlight.
@@ -2714,7 +2715,7 @@ public class Editor {
        unregisterOnBackInvokedCallback();
    }

    private void stopTextActionModeWithPreservingSelection() {
    void stopTextActionModeWithPreservingSelection() {
        if (mTextActionMode != null) {
            mRestartActionModeOnNextRefresh = true;
        }
+168 −26
Original line number Diff line number Diff line
@@ -68,6 +68,7 @@ import android.content.res.XmlResourceParser;
import android.graphics.BaseCanvas;
import android.graphics.BlendMode;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Insets;
import android.graphics.Matrix;
import android.graphics.Paint;
@@ -87,6 +88,7 @@ import android.os.AsyncTask;
import android.os.Build;
import android.os.Build.VERSION_CODES;
import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.Handler;
import android.os.LocaleList;
import android.os.Parcel;
@@ -148,6 +150,7 @@ import android.text.style.SuggestionSpan;
import android.text.style.URLSpan;
import android.text.style.UpdateAppearance;
import android.text.util.Linkify;
import android.util.ArraySet;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.FeatureFlagUtils;
@@ -199,6 +202,7 @@ import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethodManager;
import android.view.inputmethod.InsertGesture;
import android.view.inputmethod.JoinOrSplitGesture;
import android.view.inputmethod.PreviewableHandwritingGesture;
import android.view.inputmethod.RemoveSpaceGesture;
import android.view.inputmethod.SelectGesture;
import android.view.inputmethod.SelectRangeGesture;
@@ -222,6 +226,7 @@ import android.widget.RemoteViews.RemoteView;
import com.android.internal.accessibility.util.AccessibilityUtils;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.graphics.ColorUtils;
import com.android.internal.inputmethod.EditableInputConnection;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
@@ -242,6 +247,7 @@ import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
@@ -931,6 +937,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
    private List<Path> mHighlightPaths;
    private List<Paint> mHighlightPaints;
    private Highlights mHighlights;
    private int mGesturePreviewHighlightStart = -1;
    private int mGesturePreviewHighlightEnd = -1;
    private Paint mGesturePreviewHighlightPaint;
    private final List<Path> mPathRecyclePool = new ArrayList<>();
    private boolean mHighlightPathsBogus = true;
@@ -6166,6 +6175,59 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
        return mHighlights;
    }
    /**
     * Highlights the text range (from inclusive start offset to exclusive end offset) to show what
     * will be selected by the ongoing select handwriting gesture. While the gesture preview
     * highlight is shown, the selection or cursor is hidden. If the text or selection is changed,
     * the gesture preview highlight will be cleared.
     */
    private void setSelectGesturePreviewHighlight(int start, int end) {
        // Selection preview highlight color is the same as selection highlight color.
        setGesturePreviewHighlight(start, end, mHighlightColor);
    }
    /**
     * Highlights the text range (from inclusive start offset to exclusive end offset) to show what
     * will be deleted by the ongoing delete handwriting gesture. While the gesture preview
     * highlight is shown, the selection or cursor is hidden. If the text or selection is changed,
     * the gesture preview highlight will be cleared.
     */
    private void setDeleteGesturePreviewHighlight(int start, int end) {
        // Deletion preview highlight color is 20% opacity of the default text color.
        int color = mTextColor.getDefaultColor();
        color = ColorUtils.setAlphaComponent(color, (int) (0.2f * Color.alpha(color)));
        setGesturePreviewHighlight(start, end, color);
    }
    private void setGesturePreviewHighlight(int start, int end, int color) {
        mGesturePreviewHighlightStart = start;
        mGesturePreviewHighlightEnd = end;
        if (mGesturePreviewHighlightPaint == null) {
            mGesturePreviewHighlightPaint = new Paint();
            mGesturePreviewHighlightPaint.setStyle(Paint.Style.FILL);
        }
        mGesturePreviewHighlightPaint.setColor(color);
        if (mEditor != null) {
            mEditor.hideCursorAndSpanControllers();
            mEditor.stopTextActionModeWithPreservingSelection();
        }
        mHighlightPathsBogus = true;
        invalidate();
    }
    private void clearGesturePreviewHighlight() {
        mGesturePreviewHighlightStart = -1;
        mGesturePreviewHighlightEnd = -1;
        mHighlightPathsBogus = true;
        invalidate();
    }
    boolean hasGesturePreviewHighlight() {
        return mGesturePreviewHighlightStart > 0;
    }
    /**
     * Convenience method to append the specified text to the TextView's
     * display buffer, upgrading it to {@link android.widget.TextView.BufferType#EDITABLE}
@@ -8300,6 +8362,22 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
                }
            }
        }
        if (hasGesturePreviewHighlight()) {
            final Path path;
            if (mPathRecyclePool.isEmpty()) {
                path = new Path();
            } else {
                path = mPathRecyclePool.get(mPathRecyclePool.size() - 1);
                mPathRecyclePool.remove(mPathRecyclePool.size() - 1);
                path.reset();
            }
            mLayout.getSelectionPath(
                    mGesturePreviewHighlightStart, mGesturePreviewHighlightEnd, path);
            mHighlightPaths.add(path);
            mHighlightPaints.add(mGesturePreviewHighlightPaint);
        }
        mHighlightPathsBogus = false;
    }
@@ -8503,7 +8581,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
        final int cursorOffsetVertical = voffsetCursor - voffsetText;
        maybeUpdateHighlightPaths();
        Path highlight = getUpdatedHighlightPath();
        // If there is a gesture preview highlight, then the selection or cursor is not drawn.
        Path highlight = hasGesturePreviewHighlight() ? null : getUpdatedHighlightPath();
        if (mEditor != null) {
            mEditor.onDraw(canvas, layout, mHighlightPaths, mHighlightPaints, highlight,
                    mHighlightPaint, cursorOffsetVertical);
@@ -9200,6 +9279,14 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
                gestures.add(RemoveSpaceGesture.class);
                gestures.add(JoinOrSplitGesture.class);
                outAttrs.setSupportedHandwritingGestures(gestures);
                Set<Class<? extends PreviewableHandwritingGesture>> previews = new ArraySet<>();
                previews.add(SelectGesture.class);
                previews.add(SelectRangeGesture.class);
                previews.add(DeleteGesture.class);
                previews.add(DeleteRangeGesture.class);
                outAttrs.setSupportedHandwritingGesturePreviews(previews);
                return ic;
            }
        }
@@ -9410,84 +9497,131 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
        return false;
    }
    /** @hide */
    public boolean previewHandwritingGesture(
            @NonNull PreviewableHandwritingGesture gesture,
            @Nullable CancellationSignal cancellationSignal) {
        if (gesture instanceof SelectGesture) {
            performHandwritingSelectGesture((SelectGesture) gesture, /* isPreview= */ true);
        } else if (gesture instanceof SelectRangeGesture) {
            performHandwritingSelectRangeGesture(
                    (SelectRangeGesture) gesture, /* isPreview= */ true);
        } else if (gesture instanceof DeleteGesture) {
            performHandwritingDeleteGesture((DeleteGesture) gesture, /* isPreview= */ true);
        } else if (gesture instanceof DeleteRangeGesture) {
            performHandwritingDeleteRangeGesture(
                    (DeleteRangeGesture) gesture, /* isPreview= */ true);
        } else {
            return false;
        }
        if (cancellationSignal != null) {
            cancellationSignal.setOnCancelListener(this::clearGesturePreviewHighlight);
        }
        return true;
    }
    /** @hide */
    public int performHandwritingSelectGesture(@NonNull SelectGesture gesture) {
        return performHandwritingSelectGesture(gesture, /* isPreview= */ false);
    }
    private int performHandwritingSelectGesture(@NonNull SelectGesture gesture, boolean isPreview) {
        int[] range = getRangeForRect(
                convertFromScreenToContentCoordinates(gesture.getSelectionArea()),
                gesture.getGranularity());
        if (range == null) {
            return handleGestureFailure(gesture);
            return handleGestureFailure(gesture, isPreview);
        }
        return performHandwritingSelectGesture(range, isPreview);
    }
    private int performHandwritingSelectGesture(int[] range, boolean isPreview) {
        if (isPreview) {
            setSelectGesturePreviewHighlight(range[0], range[1]);
        } else {
            Selection.setSelection(getEditableText(), range[0], range[1]);
            mEditor.startSelectionActionModeAsync(/* adjustSelection= */ false);
        }
        return InputConnection.HANDWRITING_GESTURE_RESULT_SUCCESS;
    }
    /** @hide */
    public int performHandwritingSelectRangeGesture(@NonNull SelectRangeGesture gesture) {
        return performHandwritingSelectRangeGesture(gesture, /* isPreview= */ false);
    }
    private int performHandwritingSelectRangeGesture(
            @NonNull SelectRangeGesture gesture, boolean isPreview) {
        int[] startRange = getRangeForRect(
                convertFromScreenToContentCoordinates(gesture.getSelectionStartArea()),
                gesture.getGranularity());
        if (startRange == null) {
            return handleGestureFailure(gesture);
            return handleGestureFailure(gesture, isPreview);
        }
        int[] endRange = getRangeForRect(
                convertFromScreenToContentCoordinates(gesture.getSelectionEndArea()),
                gesture.getGranularity());
        if (endRange == null) {
            return handleGestureFailure(gesture);
            return handleGestureFailure(gesture, isPreview);
        }
        int[] range = new int[] {
                Math.min(startRange[0], endRange[0]), Math.max(startRange[1], endRange[1])
        };
        Selection.setSelection(getEditableText(), range[0], range[1]);
        mEditor.startSelectionActionModeAsync(/* adjustSelection= */ false);
        return InputConnection.HANDWRITING_GESTURE_RESULT_SUCCESS;
        return performHandwritingSelectGesture(range, isPreview);
    }
    /** @hide */
    public int performHandwritingDeleteGesture(@NonNull DeleteGesture gesture) {
        return performHandwritingDeleteGesture(gesture, /* isPreview= */ false);
    }
    private int performHandwritingDeleteGesture(@NonNull DeleteGesture gesture, boolean isPreview) {
        int[] range = getRangeForRect(
                convertFromScreenToContentCoordinates(gesture.getDeletionArea()),
                gesture.getGranularity());
        if (range == null) {
            return handleGestureFailure(gesture);
            return handleGestureFailure(gesture, isPreview);
        }
        return performHandwritingDeleteGesture(range, gesture.getGranularity(), isPreview);
    }
        if (gesture.getGranularity() == HandwritingGesture.GRANULARITY_WORD) {
    private int performHandwritingDeleteGesture(int[] range, int granularity, boolean isPreview) {
        if (isPreview) {
            setDeleteGesturePreviewHighlight(range[0], range[1]);
        } else {
            if (granularity == HandwritingGesture.GRANULARITY_WORD) {
                range = adjustHandwritingDeleteGestureRange(range);
            }
            getEditableText().delete(range[0], range[1]);
            Selection.setSelection(getEditableText(), range[0]);
        }
        return InputConnection.HANDWRITING_GESTURE_RESULT_SUCCESS;
    }
    /** @hide */
    public int performHandwritingDeleteRangeGesture(@NonNull DeleteRangeGesture gesture) {
        return performHandwritingDeleteRangeGesture(gesture, /* isPreview= */ false);
    }
    private int performHandwritingDeleteRangeGesture(
            @NonNull DeleteRangeGesture gesture, boolean isPreview) {
        int[] startRange = getRangeForRect(
                convertFromScreenToContentCoordinates(gesture.getDeletionStartArea()),
                gesture.getGranularity());
        if (startRange == null) {
            return handleGestureFailure(gesture);
            return handleGestureFailure(gesture, isPreview);
        }
        int[] endRange = getRangeForRect(
                convertFromScreenToContentCoordinates(gesture.getDeletionEndArea()),
                gesture.getGranularity());
        if (endRange == null) {
            return handleGestureFailure(gesture);
            return handleGestureFailure(gesture, isPreview);
        }
        int[] range = new int[] {
                Math.min(startRange[0], endRange[0]), Math.max(startRange[1], endRange[1])
        };
        if (gesture.getGranularity() == HandwritingGesture.GRANULARITY_WORD) {
            range = adjustHandwritingDeleteGestureRange(range);
        }
        getEditableText().delete(range[0], range[1]);
        Selection.setSelection(getEditableText(), range[0]);
        return InputConnection.HANDWRITING_GESTURE_RESULT_SUCCESS;
        return performHandwritingDeleteGesture(range, gesture.getGranularity(), isPreview);
    }
    private int[] adjustHandwritingDeleteGestureRange(int[] range) {
@@ -9665,7 +9799,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
    }
    private int handleGestureFailure(HandwritingGesture gesture) {
        if (!TextUtils.isEmpty(gesture.getFallbackText())) {
        return handleGestureFailure(gesture, /* isPreview= */ false);
    }
    private int handleGestureFailure(HandwritingGesture gesture, boolean isPreview) {
        clearGesturePreviewHighlight();
        if (!isPreview && !TextUtils.isEmpty(gesture.getFallbackText())) {
            getEditableText()
                    .replace(getSelectionStart(), getSelectionEnd(), gesture.getFallbackText());
            return InputConnection.HANDWRITING_GESTURE_RESULT_FALLBACK;
@@ -11699,6 +11838,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
        resetErrorChangedFlag();
        sendOnTextChanged(buffer, start, before, after);
        onTextChanged(buffer, start, before, after);
        clearGesturePreviewHighlight();
    }
    /**
@@ -11737,6 +11878,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
        }
        if (selChanged) {
            clearGesturePreviewHighlight();
            mHighlightPathBogus = true;
            if (mEditor != null && !isFocused()) mEditor.mSelectionMoved = true;
+9 −0
Original line number Diff line number Diff line
@@ -27,6 +27,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.graphics.RectF;
import android.os.Bundle;
import android.os.CancellationSignal;
import android.text.Editable;
import android.text.Selection;
import android.text.method.KeyListener;
@@ -44,6 +45,7 @@ import android.view.inputmethod.HandwritingGesture;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InsertGesture;
import android.view.inputmethod.JoinOrSplitGesture;
import android.view.inputmethod.PreviewableHandwritingGesture;
import android.view.inputmethod.RemoveSpaceGesture;
import android.view.inputmethod.SelectGesture;
import android.view.inputmethod.SelectRangeGesture;
@@ -320,6 +322,13 @@ public final class EditableInputConnection extends BaseInputConnection
        }
    }

    @Override
    public boolean previewHandwritingGesture(
            @NonNull PreviewableHandwritingGesture gesture,
            @Nullable CancellationSignal cancellationSignal) {
        return mTextView.previewHandwritingGesture(gesture, cancellationSignal);
    }

    @Override
    public void dumpDebug(ProtoOutputStream proto, long fieldId) {
        final long token = proto.start(fieldId);