From 3b47b2485060c3c2cf37612fd731feafc148f20a Mon Sep 17 00:00:00 2001 From: Haoyu Zhang Date: Thu, 5 Jan 2023 16:58:22 -0800 Subject: [PATCH] Address API feedbacks for TextBoundsInfo Bug: 262452997 Bug: 265068032 Test: atest TextBoundsInfoTest Test: atest TextBoundsInfoMethodTest Test: atest TextViewTextBoundsInfoTest Change-Id: I2105efc8a90fee7f11d5c534c18e659d821abf1c --- core/api/current.txt | 14 ++--- .../IRemoteInputConnectionInvoker.java | 6 +- .../RemoteInputConnection.java | 4 +- core/java/android/text/SegmentFinder.java | 4 +- .../view/inputmethod/InputConnection.java | 8 +-- .../inputmethod/InputConnectionWrapper.java | 4 +- .../RemoteInputConnectionImpl.java | 4 +- .../view/inputmethod/TextBoundsInfo.java | 60 ++++++++++++------- core/java/android/widget/TextView.java | 31 +++++----- .../inputmethod/EditableInputConnection.java | 4 +- .../inputmethod/IRemoteInputConnection.aidl | 2 +- 11 files changed, 79 insertions(+), 62 deletions(-) diff --git a/core/api/current.txt b/core/api/current.txt index dd3d6eb0fcd87..7ea288600fa18 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -46612,8 +46612,8 @@ package android.text { field public static final int DONE = -1; // 0xffffffff } - public static class SegmentFinder.DefaultSegmentFinder extends android.text.SegmentFinder { - ctor public SegmentFinder.DefaultSegmentFinder(@NonNull int[]); + public static class SegmentFinder.PrescribedSegmentFinder extends android.text.SegmentFinder { + ctor public SegmentFinder.PrescribedSegmentFinder(@NonNull int[]); method public int nextEndBoundary(@IntRange(from=0) int); method public int nextStartBoundary(@IntRange(from=0) int); method public int previousEndBoundary(@IntRange(from=0) int); @@ -55079,15 +55079,15 @@ package android.view.inputmethod { public final class TextBoundsInfo implements android.os.Parcelable { method public int describeContents(); method @IntRange(from=0, to=125) public int getCharacterBidiLevel(int); - method @NonNull public android.graphics.RectF getCharacterBounds(int); + method @NonNull public void getCharacterBounds(int, @NonNull android.graphics.RectF); method public int getCharacterFlags(int); - method public int getEnd(); + method public int getEndIndex(); method @NonNull public android.text.SegmentFinder getGraphemeSegmentFinder(); method @NonNull public android.text.SegmentFinder getLineSegmentFinder(); - method @NonNull public android.graphics.Matrix getMatrix(); + method @NonNull public void getMatrix(@NonNull android.graphics.Matrix); method public int getOffsetForPosition(float, float); method @Nullable public int[] getRangeForRect(@NonNull android.graphics.RectF, @NonNull android.text.SegmentFinder, @NonNull android.text.Layout.TextInclusionStrategy); - method public int getStart(); + method public int getStartIndex(); method @NonNull public android.text.SegmentFinder getWordSegmentFinder(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator CREATOR; @@ -55098,7 +55098,7 @@ package android.view.inputmethod { } public static final class TextBoundsInfo.Builder { - ctor public TextBoundsInfo.Builder(); + ctor public TextBoundsInfo.Builder(int, int); method @NonNull public android.view.inputmethod.TextBoundsInfo build(); method @NonNull public android.view.inputmethod.TextBoundsInfo.Builder clear(); method @NonNull public android.view.inputmethod.TextBoundsInfo.Builder setCharacterBidiLevel(@NonNull int[]); diff --git a/core/java/android/inputmethodservice/IRemoteInputConnectionInvoker.java b/core/java/android/inputmethodservice/IRemoteInputConnectionInvoker.java index 8759a6a263485..8e6f6dc092171 100644 --- a/core/java/android/inputmethodservice/IRemoteInputConnectionInvoker.java +++ b/core/java/android/inputmethodservice/IRemoteInputConnectionInvoker.java @@ -767,20 +767,20 @@ final class IRemoteInputConnectionInvoker { /** * Invokes {@link IRemoteInputConnection#requestTextBoundsInfo(InputConnectionCommandHeader, * RectF, ResultReceiver)} - * @param rectF {@code rectF} parameter to be passed. + * @param bounds {@code rectF} parameter to be passed. * @param executor {@code Executor} parameter to be passed. * @param consumer {@code Consumer} parameter to be passed. */ @AnyThread public void requestTextBoundsInfo( - @NonNull RectF rectF, @NonNull @CallbackExecutor Executor executor, + @NonNull RectF bounds, @NonNull @CallbackExecutor Executor executor, @NonNull Consumer consumer) { Objects.requireNonNull(executor); Objects.requireNonNull(consumer); final ResultReceiver resultReceiver = new TextBoundsInfoResultReceiver(executor, consumer); try { - mConnection.requestTextBoundsInfo(createHeader(), rectF, resultReceiver); + mConnection.requestTextBoundsInfo(createHeader(), bounds, resultReceiver); } catch (RemoteException e) { executor.execute(() -> consumer.accept(new TextBoundsInfoResult(CODE_CANCELLED))); } diff --git a/core/java/android/inputmethodservice/RemoteInputConnection.java b/core/java/android/inputmethodservice/RemoteInputConnection.java index f93f9abc3bb0b..ec26ace79cd8a 100644 --- a/core/java/android/inputmethodservice/RemoteInputConnection.java +++ b/core/java/android/inputmethodservice/RemoteInputConnection.java @@ -476,9 +476,9 @@ final class RemoteInputConnection implements InputConnection { @AnyThread public void requestTextBoundsInfo( - @NonNull RectF rectF, @NonNull @CallbackExecutor Executor executor, + @NonNull RectF bounds, @NonNull @CallbackExecutor Executor executor, @NonNull Consumer consumer) { - mInvoker.requestTextBoundsInfo(rectF, executor, consumer); + mInvoker.requestTextBoundsInfo(bounds, executor, consumer); } @AnyThread diff --git a/core/java/android/text/SegmentFinder.java b/core/java/android/text/SegmentFinder.java index be0094b28509c..047d07a2e3e01 100644 --- a/core/java/android/text/SegmentFinder.java +++ b/core/java/android/text/SegmentFinder.java @@ -74,7 +74,7 @@ public abstract class SegmentFinder { /** * The default {@link SegmentFinder} implementation based on given segment ranges. */ - public static class DefaultSegmentFinder extends SegmentFinder { + public static class PrescribedSegmentFinder extends SegmentFinder { private final int[] mSegments; /** @@ -87,7 +87,7 @@ public abstract class SegmentFinder { * @throws IllegalArgumentException if the given segments array's length is not even; the * given segments are not sorted or there are segments overlap with others. */ - public DefaultSegmentFinder(@NonNull int[] segments) { + public PrescribedSegmentFinder(@NonNull int[] segments) { checkSegmentsValid(segments); mSegments = segments; } diff --git a/core/java/android/view/inputmethod/InputConnection.java b/core/java/android/view/inputmethod/InputConnection.java index 9b519c3225e2d..687253683dcec 100644 --- a/core/java/android/view/inputmethod/InputConnection.java +++ b/core/java/android/view/inputmethod/InputConnection.java @@ -1262,13 +1262,13 @@ public interface InputConnection { /** * Called by input method to request the {@link TextBoundsInfo} for a range of text which is - * covered by or in vicinity of the given {@code RectF}. It can be used as a supplementary + * covered by or in vicinity of the given {@code bounds}. It can be used as a supplementary * method to implement the handwriting gesture API - * {@link #performHandwritingGesture(HandwritingGesture, Executor, IntConsumer)}. * *

Editor authors: It's preferred that the editor returns a * {@link TextBoundsInfo} of all the text lines whose bounds intersect with the given - * {@code rectF}. + * {@code bounds}. *

* *

IME authors: This method is expensive when the text is long. Please @@ -1276,7 +1276,7 @@ public interface InputConnection { * consuming. It's preferable to only request text bounds in smaller areas. *

* - * @param rectF the interested area where the text bounds are requested, in the screen + * @param bounds the interested area where the text bounds are requested, in the screen * coordinates. * @param executor the executor to run the callback. * @param consumer the callback invoked by editor to return the result. It must return a @@ -1286,7 +1286,7 @@ public interface InputConnection { * @see android.view.inputmethod.TextBoundsInfoResult */ default void requestTextBoundsInfo( - @NonNull RectF rectF, @NonNull @CallbackExecutor Executor executor, + @NonNull RectF bounds, @NonNull @CallbackExecutor Executor executor, @NonNull Consumer consumer) { Objects.requireNonNull(executor); Objects.requireNonNull(consumer); diff --git a/core/java/android/view/inputmethod/InputConnectionWrapper.java b/core/java/android/view/inputmethod/InputConnectionWrapper.java index 4befd6f026ca4..5e323fac2d8cb 100644 --- a/core/java/android/view/inputmethod/InputConnectionWrapper.java +++ b/core/java/android/view/inputmethod/InputConnectionWrapper.java @@ -362,9 +362,9 @@ public class InputConnectionWrapper implements InputConnection { */ @Override public void requestTextBoundsInfo( - @NonNull RectF rectF, @NonNull @CallbackExecutor Executor executor, + @NonNull RectF bounds, @NonNull @CallbackExecutor Executor executor, @NonNull Consumer consumer) { - mTarget.requestTextBoundsInfo(rectF, executor, consumer); + mTarget.requestTextBoundsInfo(bounds, executor, consumer); } /** diff --git a/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java b/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java index 7525d723b8026..6f8b422da2185 100644 --- a/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java +++ b/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java @@ -1106,7 +1106,7 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub { @Dispatching(cancellable = true) @Override public void requestTextBoundsInfo( - InputConnectionCommandHeader header, RectF rectF, + InputConnectionCommandHeader header, RectF bounds, @NonNull ResultReceiver resultReceiver) { dispatchWithTracing("requestTextBoundsInfo", () -> { if (header.mSessionId != mCurrentSessionId.get()) { @@ -1121,7 +1121,7 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub { } ic.requestTextBoundsInfo( - rectF, + bounds, Runnable::run, (textBoundsInfoResult) -> { final int resultCode = textBoundsInfoResult.getResultCode(); diff --git a/core/java/android/view/inputmethod/TextBoundsInfo.java b/core/java/android/view/inputmethod/TextBoundsInfo.java index dd055437b5611..d42d94e91933b 100644 --- a/core/java/android/view/inputmethod/TextBoundsInfo.java +++ b/core/java/android/view/inputmethod/TextBoundsInfo.java @@ -43,8 +43,8 @@ import java.util.function.Consumer; * The text bounds information of a slice of text in the editor. * *

This class provides IME the layout information of the text within the range from - * {@link #getStart()} to {@link #getEnd()}. It's intended to be used by IME as a supplementary API - * to support handwriting gestures. + * {@link #getStartIndex()} to {@link #getEndIndex()}. It's intended to be used by IME as a + * supplementary API to support handwriting gestures. *

*/ public final class TextBoundsInfo implements Parcelable { @@ -168,16 +168,13 @@ public final class TextBoundsInfo implements Parcelable { private final SegmentFinder mGraphemeSegmentFinder; /** - * Returns a new instance of {@link android.graphics.Matrix} that indicates the transformation + * Set the given {@link android.graphics.Matrix} to be the transformation * matrix that is to be applied other positional data in this class. - * - * @return a new instance (copy) of the transformation matrix. */ @NonNull - public Matrix getMatrix() { - final Matrix matrix = new Matrix(); + public void getMatrix(@NonNull Matrix matrix) { + Objects.requireNonNull(matrix); matrix.setValues(mMatrixValues); - return matrix; } /** @@ -186,7 +183,7 @@ public final class TextBoundsInfo implements Parcelable { * * @see Builder#setStartAndEnd(int, int) */ - public int getStart() { + public int getStartIndex() { return mStart; } @@ -196,28 +193,28 @@ public final class TextBoundsInfo implements Parcelable { * * @see Builder#setStartAndEnd(int, int) */ - public int getEnd() { + public int getEndIndex() { return mEnd; } /** - * Return the bounds of the character at the given {@code index}, in the coordinates of the - * editor. + * Set the bounds of the character at the given {@code index} to the given {@link RectF}, in + * the coordinates of the editor. * * @param index the index of the queried character. - * @return the bounding box of the queried character. + * @param bounds the {@link RectF} used to receive the result. * * @throws IndexOutOfBoundsException if the given {@code index} is out of the range from * the {@code start} to the {@code end}. */ @NonNull - public RectF getCharacterBounds(int index) { + public void getCharacterBounds(int index, @NonNull RectF bounds) { if (index < mStart || index >= mEnd) { throw new IndexOutOfBoundsException("Index is out of the bounds of " + "[" + mStart + ", " + mEnd + ")."); } final int offset = 4 * (index - mStart); - return new RectF(mCharacterBounds[offset], mCharacterBounds[offset + 1], + bounds.set(mCharacterBounds[offset], mCharacterBounds[offset + 1], mCharacterBounds[offset + 2], mCharacterBounds[offset + 3]); } @@ -333,6 +330,16 @@ public final class TextBoundsInfo implements Parcelable { * won't check the text in the ranges of [5, 7) and [12, 15). *

* + *

Under the following conditions, this method will return -1 indicating that no valid + * character is found: + *

    + *
  • The given {@code y} coordinate is above the first line or below the last line (the + * first line or the last line is identified by the {@link SegmentFinder} returned from + * {@link #getLineSegmentFinder()}).
  • + *
  • There is no character in this {@link TextBoundsInfo}.
  • + *
+ *

+ * * @param x the x coordinates of the interested location, in the editor's coordinates. * @param y the y coordinates of the interested location, in the editor's coordinates. * @return the index of the character whose position is closest to the given location. It will @@ -990,8 +997,8 @@ public final class TextBoundsInfo implements Parcelable { public static final class Builder { private final float[] mMatrixValues = new float[9]; private boolean mMatrixInitialized; - private int mStart; - private int mEnd; + private int mStart = -1; + private int mEnd = -1; private float[] mCharacterBounds; private int[] mCharacterFlags; private int[] mCharacterBidiLevels; @@ -999,6 +1006,17 @@ public final class TextBoundsInfo implements Parcelable { private SegmentFinder mWordSegmentFinder; private SegmentFinder mGraphemeSegmentFinder; + /** + * Create a builder for {@link TextBoundsInfo}. + * @param start the start index of the {@link TextBoundsInfo}, inclusive. + * @param end the end index of the {@link TextBoundsInfo}, exclusive. + * @throws IllegalArgumentException if the given {@code start} or {@code end} is negative, + * or {@code end} is smaller than the {@code start}. + */ + public Builder(int start, int end) { + setStartAndEnd(start, end); + } + /** Clear all the parameters set on this {@link Builder} to reuse it. */ @NonNull public Builder clear() { @@ -1152,7 +1170,7 @@ public final class TextBoundsInfo implements Parcelable { * * @see #getGraphemeSegmentFinder() * @see SegmentFinder - * @see SegmentFinder.DefaultSegmentFinder + * @see SegmentFinder.PrescribedSegmentFinder */ @NonNull public Builder setGraphemeSegmentFinder(@NonNull SegmentFinder graphemeSegmentFinder) { @@ -1171,7 +1189,7 @@ public final class TextBoundsInfo implements Parcelable { * * @see #getWordSegmentFinder() * @see SegmentFinder - * @see SegmentFinder.DefaultSegmentFinder + * @see SegmentFinder.PrescribedSegmentFinder */ @NonNull public Builder setWordSegmentFinder(@NonNull SegmentFinder wordSegmentFinder) { @@ -1193,7 +1211,7 @@ public final class TextBoundsInfo implements Parcelable { * * @see #getLineSegmentFinder() * @see SegmentFinder - * @see SegmentFinder.DefaultSegmentFinder + * @see SegmentFinder.PrescribedSegmentFinder */ @NonNull public Builder setLineSegmentFinder(@NonNull SegmentFinder lineSegmentFinder) { @@ -1360,7 +1378,7 @@ public final class TextBoundsInfo implements Parcelable { breaks = GrowingArrayUtils.append(breaks, count++, start + offset); } } - return new SegmentFinder.DefaultSegmentFinder(Arrays.copyOf(breaks, count)); + return new SegmentFinder.PrescribedSegmentFinder(Arrays.copyOf(breaks, count)); } /** diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index b9b928e4c2f96..4005bc86af39c 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -13245,7 +13245,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * Creates the {@link TextBoundsInfo} for the text lines that intersects with the {@code rectF}. * @hide */ - public TextBoundsInfo getTextBoundsInfo(@NonNull RectF rectF) { + public TextBoundsInfo getTextBoundsInfo(@NonNull RectF bounds) { final Layout layout = getLayout(); if (layout == null) { // No valid text layout, return null. @@ -13268,19 +13268,18 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener final float layoutLeft = viewportToContentHorizontalOffset(); final float layoutTop = viewportToContentVerticalOffset(); - final RectF localRectF = new RectF(rectF); - globalToLocalMatrix.mapRect(localRectF); - localRectF.offset(-layoutLeft, -layoutTop); + final RectF localBounds = new RectF(bounds); + globalToLocalMatrix.mapRect(localBounds); + localBounds.offset(-layoutLeft, -layoutTop); // Text length is 0. There is no character bounds, return empty TextBoundsInfo. // rectF doesn't intersect with the layout, return empty TextBoundsInfo. - if (!localRectF.intersects(0f, 0f, layout.getWidth(), layout.getHeight()) + if (!localBounds.intersects(0f, 0f, layout.getWidth(), layout.getHeight()) || text.length() == 0) { - final TextBoundsInfo.Builder builder = new TextBoundsInfo.Builder(); + final TextBoundsInfo.Builder builder = new TextBoundsInfo.Builder(0, 0); final SegmentFinder emptySegmentFinder = - new SegmentFinder.DefaultSegmentFinder(new int[0]); - builder.setStartAndEnd(0, 0) - .setMatrix(localToGlobalMatrix) + new SegmentFinder.PrescribedSegmentFinder(new int[0]); + builder.setMatrix(localToGlobalMatrix) .setCharacterBounds(new float[0]) .setCharacterBidiLevel(new int[0]) .setCharacterFlags(new int[0]) @@ -13290,8 +13289,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return builder.build(); } - final int startLine = layout.getLineForVertical((int) Math.floor(localRectF.top)); - final int endLine = layout.getLineForVertical((int) Math.floor(localRectF.bottom)); + final int startLine = layout.getLineForVertical((int) Math.floor(localBounds.top)); + final int endLine = layout.getLineForVertical((int) Math.floor(localBounds.bottom)); final int start = layout.getLineStart(startLine); final int end = layout.getLineEnd(endLine); @@ -13349,18 +13348,18 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener lineRanges[2 * offset] = layout.getLineStart(line); lineRanges[2 * offset + 1] = layout.getLineEnd(line); } - final SegmentFinder lineSegmentFinder = new SegmentFinder.DefaultSegmentFinder(lineRanges); + final SegmentFinder lineSegmentFinder = + new SegmentFinder.PrescribedSegmentFinder(lineRanges); - final TextBoundsInfo.Builder builder = new TextBoundsInfo.Builder(); - builder.setStartAndEnd(start, end) + return new TextBoundsInfo.Builder(start, end) .setMatrix(localToGlobalMatrix) .setCharacterBounds(characterBounds) .setCharacterBidiLevel(characterBidiLevels) .setCharacterFlags(characterFlags) .setGraphemeSegmentFinder(graphemeSegmentFinder) .setLineSegmentFinder(lineSegmentFinder) - .setWordSegmentFinder(wordSegmentFinder); - return builder.build(); + .setWordSegmentFinder(wordSegmentFinder) + .build(); } /** diff --git a/core/java/com/android/internal/inputmethod/EditableInputConnection.java b/core/java/com/android/internal/inputmethod/EditableInputConnection.java index 52e7471507896..8690e8d0d9d0b 100644 --- a/core/java/com/android/internal/inputmethod/EditableInputConnection.java +++ b/core/java/com/android/internal/inputmethod/EditableInputConnection.java @@ -271,9 +271,9 @@ public final class EditableInputConnection extends BaseInputConnection @Override public void requestTextBoundsInfo( - @NonNull RectF rectF, @Nullable @CallbackExecutor Executor executor, + @NonNull RectF bounds, @Nullable @CallbackExecutor Executor executor, @NonNull Consumer consumer) { - final TextBoundsInfo textBoundsInfo = mTextView.getTextBoundsInfo(rectF); + final TextBoundsInfo textBoundsInfo = mTextView.getTextBoundsInfo(bounds); final int resultCode; if (textBoundsInfo != null) { resultCode = TextBoundsInfoResult.CODE_SUCCESS; diff --git a/core/java/com/android/internal/inputmethod/IRemoteInputConnection.aidl b/core/java/com/android/internal/inputmethod/IRemoteInputConnection.aidl index 65016c27575ec..b375936860a86 100644 --- a/core/java/com/android/internal/inputmethod/IRemoteInputConnection.aidl +++ b/core/java/com/android/internal/inputmethod/IRemoteInputConnection.aidl @@ -111,7 +111,7 @@ import com.android.internal.inputmethod.InputConnectionCommandHeader; int cursorUpdateMode, int cursorUpdateFilter, int imeDisplayId, in AndroidFuture future /* T=Boolean */); - void requestTextBoundsInfo(in InputConnectionCommandHeader header, in RectF rect, + void requestTextBoundsInfo(in InputConnectionCommandHeader header, in RectF bounds, in ResultReceiver resultReceiver /* T=TextBoundsInfoResult */); void commitContent(in InputConnectionCommandHeader header, in InputContentInfo inputContentInfo,