Merge "Support InsertModeGesture"

This commit is contained in:
Haoyu Zhang
2023-01-19 12:03:30 +00:00
committed by Android (Google) Code Review
3 changed files with 139 additions and 1 deletions

View File

@@ -73,10 +73,12 @@ import android.text.Spanned;
import android.text.SpannedString;
import android.text.StaticLayout;
import android.text.TextUtils;
import android.text.method.InsertModeTransformationMethod;
import android.text.method.KeyListener;
import android.text.method.MetaKeyKeyListener;
import android.text.method.MovementMethod;
import android.text.method.OffsetMapping;
import android.text.method.TransformationMethod;
import android.text.method.WordIterator;
import android.text.style.EasyEditSpan;
import android.text.style.SuggestionRangeSpan;
@@ -136,6 +138,7 @@ import android.window.OnBackInvokedCallback;
import android.window.OnBackInvokedDispatcher;
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;
@@ -461,6 +464,7 @@ public class Editor {
private int mLineChangeSlopMin;
private final AccessibilitySmartActions mA11ySmartActions;
private InsertModeController mInsertModeController;
Editor(TextView textView) {
mTextView = textView;
@@ -2109,6 +2113,10 @@ public class Editor {
}
}
if (mInsertModeController != null) {
mInsertModeController.onDraw(canvas);
}
if (mTextView.canHaveDisplayList() && canvas.isHardwareAccelerated()) {
drawHardwareAccelerated(canvas, layout, highlightPaths, highlightPaints,
selectionHighlight, selectionHighlightPaint, cursorOffsetVertical);
@@ -8054,6 +8062,110 @@ public class Editor {
}
}
private static final class InsertModeController {
private final TextView mTextView;
private boolean mIsInsertModeActive;
private InsertModeTransformationMethod mInsertModeTransformationMethod;
private final Paint mHighlightPaint;
InsertModeController(@NonNull TextView textView) {
mTextView = Objects.requireNonNull(textView);
mIsInsertModeActive = false;
mInsertModeTransformationMethod = null;
mHighlightPaint = new Paint();
// The highlight color is supposed to be 12% of the color primary40. We can't
// directly access Material 3 theme. But because Material 3 sets the colorPrimary to
// be primary40, here we hardcoded it to be 12% of colorPrimary.
final TypedValue typedValue = new TypedValue();
mTextView.getContext().getTheme()
.resolveAttribute(R.attr.colorPrimary, typedValue, true);
final int colorPrimary = typedValue.data;
final int highlightColor = ColorUtils.setAlphaComponent(colorPrimary,
(int) (0.12f * Color.alpha(colorPrimary)));
mHighlightPaint.setColor(highlightColor);
}
/**
* Enter insert mode.
* @param offset the index to set the cursor.
* @return true if the call is successful. false if a) it's already in the insert mode,
* b) it failed to enter the insert mode.
*/
boolean enterInsertMode(int offset) {
if (mIsInsertModeActive) return false;
TransformationMethod oldTransformationMethod =
mTextView.getTransformationMethod();
if (oldTransformationMethod instanceof OffsetMapping) {
// We can't support the case where the oldTransformationMethod is an OffsetMapping.
return false;
}
final boolean isSingleLine = mTextView.isSingleLine();
mInsertModeTransformationMethod = new InsertModeTransformationMethod(offset,
isSingleLine, oldTransformationMethod);
mTextView.setTransformationMethod(mInsertModeTransformationMethod);
Selection.setSelection((Spannable) mTextView.getText(), offset);
mIsInsertModeActive = true;
return true;
}
void exitInsertMode() {
if (!mIsInsertModeActive) return;
if (mInsertModeTransformationMethod == null
|| mInsertModeTransformationMethod != mTextView.getTransformationMethod()) {
// If mInsertionModeTransformationMethod doesn't match the one on TextView,
// something else have changed the TextView's TransformationMethod while the
// insertion mode is active. We don't need to restore the oldTransformationMethod.
// TODO(265871733): support the case where setTransformationMethod is called in
// the insert mode.
mIsInsertModeActive = false;
return;
}
// Changing TransformationMethod will reset selection range to [0, 0), we need to
// manually restore the old selection range.
final int selectionStart = mTextView.getSelectionStart();
final int selectionEnd = mTextView.getSelectionEnd();
final TransformationMethod oldTransformationMethod =
mInsertModeTransformationMethod.getOldTransformationMethod();
mTextView.setTransformationMethod(oldTransformationMethod);
Selection.setSelection((Spannable) mTextView.getText(), selectionStart, selectionEnd);
mIsInsertModeActive = false;
}
void onDraw(Canvas canvas) {
if (!mIsInsertModeActive) return;
final CharSequence transformedText = mTextView.getTransformed();
if (transformedText instanceof InsertModeTransformationMethod.TransformedText) {
final Layout layout = mTextView.getLayout();
if (layout == null) return;
final InsertModeTransformationMethod.TransformedText insertModeTransformedText =
((InsertModeTransformationMethod.TransformedText) transformedText);
final int highlightStart = insertModeTransformedText.getHighlightStart();
final int highlightEnd = insertModeTransformedText.getHighlightEnd();
final Layout.SelectionRectangleConsumer consumer =
(left, top, right, bottom, textSelectionLayout) ->
canvas.drawRect(left, top, right, bottom, mHighlightPaint);
layout.getSelection(highlightStart, highlightEnd, consumer);
}
}
}
boolean enterInsertMode(int offset) {
if (mInsertModeController == null) {
if (mTextView == null) return false;
mInsertModeController = new InsertModeController(mTextView);
}
return mInsertModeController.enterInsertMode(offset);
}
void exitInsertMode() {
if (mInsertModeController == null) return;
mInsertModeController.exitInsertMode();
}
/**
* Initializes the nodeInfo with smart actions.
*/

View File

@@ -202,6 +202,7 @@ import android.view.inputmethod.HandwritingGesture;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethodManager;
import android.view.inputmethod.InsertGesture;
import android.view.inputmethod.InsertModeGesture;
import android.view.inputmethod.JoinOrSplitGesture;
import android.view.inputmethod.PreviewableHandwritingGesture;
import android.view.inputmethod.RemoveSpaceGesture;
@@ -2534,7 +2535,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
/**
* @hide
*/
@VisibleForTesting
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
public CharSequence getTransformed() {
return mTransformed;
}
@@ -9620,6 +9621,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
gestures.add(InsertGesture.class);
gestures.add(RemoveSpaceGesture.class);
gestures.add(JoinOrSplitGesture.class);
gestures.add(InsertModeGesture.class);
outAttrs.setSupportedHandwritingGestures(gestures);
Set<Class<? extends PreviewableHandwritingGesture>> previews = new ArraySet<>();
@@ -10169,6 +10171,27 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
return InputConnection.HANDWRITING_GESTURE_RESULT_SUCCESS;
}
/** @hide */
public int performHandwritingInsertModeGesture(@NonNull InsertModeGesture gesture) {
final PointF insertPoint =
convertFromScreenToContentCoordinates(gesture.getInsertionPoint());
final int line = getLineForHandwritingGesture(insertPoint);
final CancellationSignal cancellationSignal = gesture.getCancellationSignal();
// If no cancellationSignal is provided, don't enter the insert mode.
if (line == -1 || cancellationSignal == null) {
return handleGestureFailure(gesture);
}
final int offset = mLayout.getOffsetForHorizontal(line, insertPoint.x);
if (!mEditor.enterInsertMode(offset)) {
return InputConnection.HANDWRITING_GESTURE_RESULT_FAILED;
}
cancellationSignal.setOnCancelListener(() -> mEditor.exitInsertMode());
return InputConnection.HANDWRITING_GESTURE_RESULT_SUCCESS;
}
private int handleGestureFailure(HandwritingGesture gesture) {
return handleGestureFailure(gesture, /* isPreview= */ false);
}

View File

@@ -44,6 +44,7 @@ import android.view.inputmethod.ExtractedTextRequest;
import android.view.inputmethod.HandwritingGesture;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InsertGesture;
import android.view.inputmethod.InsertModeGesture;
import android.view.inputmethod.JoinOrSplitGesture;
import android.view.inputmethod.PreviewableHandwritingGesture;
import android.view.inputmethod.RemoveSpaceGesture;
@@ -314,6 +315,8 @@ public final class EditableInputConnection extends BaseInputConnection
result = mTextView.performHandwritingRemoveSpaceGesture((RemoveSpaceGesture) gesture);
} else if (gesture instanceof JoinOrSplitGesture) {
result = mTextView.performHandwritingJoinOrSplitGesture((JoinOrSplitGesture) gesture);
} else if (gesture instanceof InsertModeGesture) {
result = mTextView.performHandwritingInsertModeGesture((InsertModeGesture) gesture);
} else {
result = HANDWRITING_GESTURE_RESULT_UNSUPPORTED;
}