diff options
| author | 2022-12-03 06:02:41 +0000 | |
|---|---|---|
| committer | 2022-12-03 06:02:41 +0000 | |
| commit | 76ecb08b70dcd1c27aff19556f17430eb656e14c (patch) | |
| tree | f55d664bf01f4da88cc113297624950ca043b78f | |
| parent | 6de6de3622ce1111ff20f7891d4be1608c769e94 (diff) | |
| parent | 691cbb963ae554b1a767885ce48dde3ec58b6aaf (diff) | |
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
| -rw-r--r-- | core/java/android/widget/Editor.java | 5 | ||||
| -rw-r--r-- | core/java/android/widget/TextView.java | 194 | ||||
| -rw-r--r-- | core/java/com/android/internal/inputmethod/EditableInputConnection.java | 9 |
3 files changed, 180 insertions, 28 deletions
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java index 558d96089bb7..0a3ea8a9f356 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -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; } diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 475a8491de4d..b9b928e4c2f9 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -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; @@ -6167,6 +6176,59 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } /** + * 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} * if it was not already 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; } } @@ -9411,83 +9498,130 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } /** @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); } - 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) { - range = adjustHandwritingDeleteGestureRange(range); - } + 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]); + 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; diff --git a/core/java/com/android/internal/inputmethod/EditableInputConnection.java b/core/java/com/android/internal/inputmethod/EditableInputConnection.java index 330ff8e61609..52e747150789 100644 --- a/core/java/com/android/internal/inputmethod/EditableInputConnection.java +++ b/core/java/com/android/internal/inputmethod/EditableInputConnection.java @@ -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; @@ -321,6 +323,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); CharSequence editableText = mTextView.getText(); |