summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Justin Ghan <justinghan@google.com> 2022-12-03 06:02:41 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2022-12-03 06:02:41 +0000
commit76ecb08b70dcd1c27aff19556f17430eb656e14c (patch)
treef55d664bf01f4da88cc113297624950ca043b78f
parent6de6de3622ce1111ff20f7891d4be1608c769e94 (diff)
parent691cbb963ae554b1a767885ce48dde3ec58b6aaf (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.java5
-rw-r--r--core/java/android/widget/TextView.java194
-rw-r--r--core/java/com/android/internal/inputmethod/EditableInputConnection.java9
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();