Merge "Introduce TextBoundsInfo"
This commit is contained in:
@@ -45582,6 +45582,14 @@ 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[]);
|
||||
method public int nextEndBoundary(@IntRange(from=0) int);
|
||||
method public int nextStartBoundary(@IntRange(from=0) int);
|
||||
method public int previousEndBoundary(@IntRange(from=0) int);
|
||||
method public int previousStartBoundary(@IntRange(from=0) int);
|
||||
}
|
||||
|
||||
public class Selection {
|
||||
method public static boolean extendDown(android.text.Spannable, android.text.Layout);
|
||||
method public static boolean extendLeft(android.text.Spannable, android.text.Layout);
|
||||
@@ -53603,6 +53611,7 @@ package android.view.inputmethod {
|
||||
method public boolean reportFullscreenMode(boolean);
|
||||
method public boolean requestCursorUpdates(int);
|
||||
method public default boolean requestCursorUpdates(int, int);
|
||||
method public default void requestTextBoundsInfo(@NonNull android.graphics.RectF, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.view.inputmethod.TextBoundsInfoResult>);
|
||||
method public boolean sendKeyEvent(android.view.KeyEvent);
|
||||
method public boolean setComposingRegion(int, int);
|
||||
method public default boolean setComposingRegion(int, int, @Nullable android.view.inputmethod.TextAttribute);
|
||||
@@ -53932,6 +53941,50 @@ package android.view.inputmethod {
|
||||
method @NonNull public android.view.inputmethod.TextAttribute.Builder setTextConversionSuggestions(@NonNull java.util.List<java.lang.String>);
|
||||
}
|
||||
|
||||
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 public int getCharacterFlags(int);
|
||||
method public int getEnd();
|
||||
method @NonNull public android.text.SegmentFinder getGraphemeSegmentFinder();
|
||||
method @NonNull public android.text.SegmentFinder getLineSegmentFinder();
|
||||
method @NonNull public android.graphics.Matrix getMatrix();
|
||||
method public int getStart();
|
||||
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<android.view.inputmethod.TextBoundsInfo> CREATOR;
|
||||
field public static final int FLAG_CHARACTER_LINEFEED = 2; // 0x2
|
||||
field public static final int FLAG_CHARACTER_PUNCTUATION = 4; // 0x4
|
||||
field public static final int FLAG_CHARACTER_WHITESPACE = 1; // 0x1
|
||||
field public static final int FLAG_LINE_IS_RTL = 8; // 0x8
|
||||
}
|
||||
|
||||
public static final class TextBoundsInfo.Builder {
|
||||
ctor public TextBoundsInfo.Builder();
|
||||
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[]);
|
||||
method @NonNull public android.view.inputmethod.TextBoundsInfo.Builder setCharacterBounds(@NonNull float[]);
|
||||
method @NonNull public android.view.inputmethod.TextBoundsInfo.Builder setCharacterFlags(@NonNull int[]);
|
||||
method @NonNull public android.view.inputmethod.TextBoundsInfo.Builder setGraphemeSegmentFinder(@NonNull android.text.SegmentFinder);
|
||||
method @NonNull public android.view.inputmethod.TextBoundsInfo.Builder setLineSegmentFinder(@NonNull android.text.SegmentFinder);
|
||||
method @NonNull public android.view.inputmethod.TextBoundsInfo.Builder setMatrix(@NonNull android.graphics.Matrix);
|
||||
method @NonNull public android.view.inputmethod.TextBoundsInfo.Builder setStartAndEnd(@IntRange(from=0) int, @IntRange(from=0) int);
|
||||
method @NonNull public android.view.inputmethod.TextBoundsInfo.Builder setWordSegmentFinder(@NonNull android.text.SegmentFinder);
|
||||
}
|
||||
|
||||
public final class TextBoundsInfoResult {
|
||||
ctor public TextBoundsInfoResult(int);
|
||||
ctor public TextBoundsInfoResult(int, @NonNull android.view.inputmethod.TextBoundsInfo);
|
||||
method public int getResultCode();
|
||||
method @Nullable public android.view.inputmethod.TextBoundsInfo getTextBoundsInfo();
|
||||
field public static final int CODE_CANCELLED = 3; // 0x3
|
||||
field public static final int CODE_FAILED = 2; // 0x2
|
||||
field public static final int CODE_SUCCESS = 1; // 0x1
|
||||
field public static final int CODE_UNSUPPORTED = 0; // 0x0
|
||||
}
|
||||
|
||||
public final class TextSnapshot {
|
||||
ctor public TextSnapshot(@NonNull android.view.inputmethod.SurroundingText, @IntRange(from=0xffffffff) int, @IntRange(from=0xffffffff) int, int);
|
||||
method @IntRange(from=0xffffffff) public int getCompositionEnd();
|
||||
|
||||
@@ -16,10 +16,13 @@
|
||||
|
||||
package android.inputmethodservice;
|
||||
|
||||
import static android.view.inputmethod.TextBoundsInfoResult.CODE_CANCELLED;
|
||||
|
||||
import android.annotation.AnyThread;
|
||||
import android.annotation.CallbackExecutor;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.graphics.RectF;
|
||||
import android.os.Bundle;
|
||||
import android.os.RemoteException;
|
||||
import android.os.ResultReceiver;
|
||||
@@ -34,6 +37,8 @@ import android.view.inputmethod.InputContentInfo;
|
||||
import android.view.inputmethod.ParcelableHandwritingGesture;
|
||||
import android.view.inputmethod.SurroundingText;
|
||||
import android.view.inputmethod.TextAttribute;
|
||||
import android.view.inputmethod.TextBoundsInfo;
|
||||
import android.view.inputmethod.TextBoundsInfoResult;
|
||||
|
||||
import com.android.internal.infra.AndroidFuture;
|
||||
import com.android.internal.inputmethod.IRemoteInputConnection;
|
||||
@@ -41,6 +46,7 @@ import com.android.internal.inputmethod.InputConnectionCommandHeader;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.IntConsumer;
|
||||
|
||||
/**
|
||||
@@ -91,6 +97,44 @@ final class IRemoteInputConnectionInvoker {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Subclass of {@link ResultReceiver} used by
|
||||
* {@link #requestTextBoundsInfo(RectF, Executor, Consumer)} for providing
|
||||
* callback.
|
||||
*/
|
||||
private static final class TextBoundsInfoResultReceiver extends ResultReceiver {
|
||||
@Nullable
|
||||
private Consumer<TextBoundsInfoResult> mConsumer;
|
||||
@Nullable
|
||||
private Executor mExecutor;
|
||||
|
||||
TextBoundsInfoResultReceiver(@NonNull Executor executor,
|
||||
@NonNull Consumer<TextBoundsInfoResult> consumer) {
|
||||
super(null);
|
||||
mExecutor = executor;
|
||||
mConsumer = consumer;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onReceiveResult(@TextBoundsInfoResult.ResultCode int resultCode,
|
||||
@Nullable Bundle resultData) {
|
||||
synchronized (this) {
|
||||
if (mExecutor != null && mConsumer != null) {
|
||||
final TextBoundsInfoResult textBoundsInfoResult = new TextBoundsInfoResult(
|
||||
resultCode, TextBoundsInfo.createFromBundle(resultData));
|
||||
mExecutor.execute(() -> mConsumer.accept(textBoundsInfoResult));
|
||||
// provide callback only once.
|
||||
clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void clear() {
|
||||
mExecutor = null;
|
||||
mConsumer = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance of {@link IRemoteInputConnectionInvoker} for the given
|
||||
* {@link IRemoteInputConnection}.
|
||||
@@ -698,6 +742,28 @@ final class IRemoteInputConnectionInvoker {
|
||||
return future;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes {@link IRemoteInputConnection#requestTextBoundsInfo(InputConnectionCommandHeader,
|
||||
* RectF, ResultReceiver)}
|
||||
* @param rectF {@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 Consumer<TextBoundsInfoResult> consumer) {
|
||||
Objects.requireNonNull(executor);
|
||||
Objects.requireNonNull(consumer);
|
||||
|
||||
final ResultReceiver resultReceiver = new TextBoundsInfoResultReceiver(executor, consumer);
|
||||
try {
|
||||
mConnection.requestTextBoundsInfo(createHeader(), rectF, resultReceiver);
|
||||
} catch (RemoteException e) {
|
||||
executor.execute(() -> consumer.accept(new TextBoundsInfoResult(CODE_CANCELLED)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes {@link IRemoteInputConnection#commitContent(InputConnectionCommandHeader,
|
||||
* InputContentInfo, int, Bundle, AndroidFuture)}.
|
||||
|
||||
@@ -21,6 +21,7 @@ import android.annotation.CallbackExecutor;
|
||||
import android.annotation.IntRange;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.graphics.RectF;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.util.Log;
|
||||
@@ -35,6 +36,7 @@ import android.view.inputmethod.InputContentInfo;
|
||||
import android.view.inputmethod.ParcelableHandwritingGesture;
|
||||
import android.view.inputmethod.SurroundingText;
|
||||
import android.view.inputmethod.TextAttribute;
|
||||
import android.view.inputmethod.TextBoundsInfoResult;
|
||||
|
||||
import com.android.internal.inputmethod.CancellationGroup;
|
||||
import com.android.internal.inputmethod.CompletableFutureUtil;
|
||||
@@ -45,6 +47,7 @@ import com.android.internal.inputmethod.InputConnectionProtoDumper;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.IntConsumer;
|
||||
|
||||
/**
|
||||
@@ -461,6 +464,13 @@ final class RemoteInputConnection implements InputConnection {
|
||||
mCancellationGroup, MAX_WAIT_TIME_MILLIS);
|
||||
}
|
||||
|
||||
@AnyThread
|
||||
public void requestTextBoundsInfo(
|
||||
@NonNull RectF rectF, @NonNull @CallbackExecutor Executor executor,
|
||||
@NonNull Consumer<TextBoundsInfoResult> consumer) {
|
||||
mInvoker.requestTextBoundsInfo(rectF, executor, consumer);
|
||||
}
|
||||
|
||||
@AnyThread
|
||||
public Handler getHandler() {
|
||||
// Nothing should happen when called from input method.
|
||||
|
||||
@@ -19,6 +19,13 @@ package android.text;
|
||||
import android.annotation.IntRange;
|
||||
import android.graphics.RectF;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.android.internal.util.Preconditions;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Finds text segment boundaries within text. Subclasses can implement different types of text
|
||||
* segments. Grapheme clusters and words are examples of possible text segments. These are
|
||||
@@ -63,4 +70,144 @@ public abstract class SegmentFinder {
|
||||
* character offset, or {@code DONE} if there are none.
|
||||
*/
|
||||
public abstract int nextEndBoundary(@IntRange(from = 0) int offset);
|
||||
|
||||
/**
|
||||
* The default {@link SegmentFinder} implementation based on given segment ranges.
|
||||
*/
|
||||
public static class DefaultSegmentFinder extends SegmentFinder {
|
||||
private final int[] mSegments;
|
||||
|
||||
/**
|
||||
* Create a SegmentFinder with segments stored in an array, where i-th segment's start is
|
||||
* stored at segments[2 * i] and end is stored at segments[2 * i + 1] respectively.
|
||||
*
|
||||
* <p> It is required that segments do not overlap, and are already sorted by their start
|
||||
* indices. </p>
|
||||
* @param segments the array that stores the segment ranges.
|
||||
* @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) {
|
||||
checkSegmentsValid(segments);
|
||||
mSegments = segments;
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public int previousStartBoundary(@IntRange(from = 0) int offset) {
|
||||
return findPrevious(offset, /* isStart = */ true);
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public int previousEndBoundary(@IntRange(from = 0) int offset) {
|
||||
return findPrevious(offset, /* isStart = */ false);
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public int nextStartBoundary(@IntRange(from = 0) int offset) {
|
||||
return findNext(offset, /* isStart = */ true);
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
public int nextEndBoundary(@IntRange(from = 0) int offset) {
|
||||
return findNext(offset, /* isStart = */ false);
|
||||
}
|
||||
|
||||
private int findNext(int offset, boolean isStart) {
|
||||
if (offset < 0) return DONE;
|
||||
if (mSegments.length < 1 || offset > mSegments[mSegments.length - 1]) return DONE;
|
||||
|
||||
if (offset < mSegments[0]) {
|
||||
return isStart ? mSegments[0] : mSegments[1];
|
||||
}
|
||||
|
||||
int index = Arrays.binarySearch(mSegments, offset);
|
||||
if (index >= 0) {
|
||||
// mSegments may have duplicate elements (The previous segments end equals
|
||||
// to the following segments start.) Move the index forwards since we are searching
|
||||
// for the next segment.
|
||||
if (index + 1 < mSegments.length && mSegments[index + 1] == offset) {
|
||||
index = index + 1;
|
||||
}
|
||||
// Point the index to the first segment boundary larger than the given offset.
|
||||
index += 1;
|
||||
} else {
|
||||
// binarySearch returns the insertion point, it's the first segment boundary larger
|
||||
// than the given offset.
|
||||
index = -(index + 1);
|
||||
}
|
||||
if (index >= mSegments.length) return DONE;
|
||||
|
||||
// +---------------------------------------+
|
||||
// | | isStart | isEnd |
|
||||
// |---------------+-----------+-----------|
|
||||
// | indexIsStart | index | index + 1 |
|
||||
// |---------------+-----------+-----------|
|
||||
// | indexIsEnd | index + 1 | index |
|
||||
// +---------------------------------------+
|
||||
boolean indexIsStart = index % 2 == 0;
|
||||
if (isStart != indexIsStart) {
|
||||
return (index + 1 < mSegments.length) ? mSegments[index + 1] : DONE;
|
||||
}
|
||||
return mSegments[index];
|
||||
}
|
||||
|
||||
private int findPrevious(int offset, boolean isStart) {
|
||||
if (mSegments.length < 1 || offset < mSegments[0]) return DONE;
|
||||
|
||||
if (offset > mSegments[mSegments.length - 1]) {
|
||||
return isStart ? mSegments[mSegments.length - 2] : mSegments[mSegments.length - 1];
|
||||
}
|
||||
|
||||
int index = Arrays.binarySearch(mSegments, offset);
|
||||
if (index >= 0) {
|
||||
// mSegments may have duplicate elements (when the previous segments end equal
|
||||
// to the following segments start). Move the index backwards since we are searching
|
||||
// for the previous segment.
|
||||
if (index > 0 && mSegments[index - 1] == offset) {
|
||||
index = index - 1;
|
||||
}
|
||||
// Point the index to the first segment boundary smaller than the given offset.
|
||||
index -= 1;
|
||||
} else {
|
||||
// binarySearch returns the insertion point, insertionPoint - 1 is the first
|
||||
// segment boundary smaller than the given offset.
|
||||
index = -(index + 1) - 1;
|
||||
}
|
||||
if (index < 0) return DONE;
|
||||
|
||||
// +---------------------------------------+
|
||||
// | | isStart | isEnd |
|
||||
// |---------------+-----------+-----------|
|
||||
// | indexIsStart | index | index - 1 |
|
||||
// |---------------+-----------+-----------|
|
||||
// | indexIsEnd | index - 1 | index |
|
||||
// +---------------------------------------+
|
||||
boolean indexIsStart = index % 2 == 0;
|
||||
if (isStart != indexIsStart) {
|
||||
return (index > 0) ? mSegments[index - 1] : DONE;
|
||||
}
|
||||
return mSegments[index];
|
||||
}
|
||||
|
||||
private static void checkSegmentsValid(int[] segments) {
|
||||
Objects.requireNonNull(segments);
|
||||
Preconditions.checkArgument(segments.length % 2 == 0,
|
||||
"the length of segments must be even");
|
||||
if (segments.length == 0) return;
|
||||
int lastSegmentEnd = Integer.MIN_VALUE;
|
||||
for (int index = 0; index < segments.length; index += 2) {
|
||||
if (segments[index] < lastSegmentEnd) {
|
||||
throw new IllegalArgumentException("segments can't overlap");
|
||||
}
|
||||
if (segments[index] >= segments[index + 1]) {
|
||||
throw new IllegalArgumentException("the segment range can't be empty");
|
||||
}
|
||||
lastSegmentEnd = segments[index + 1];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,11 +16,14 @@
|
||||
|
||||
package android.view.inputmethod;
|
||||
|
||||
import static android.view.inputmethod.TextBoundsInfoResult.CODE_UNSUPPORTED;
|
||||
|
||||
import android.annotation.CallbackExecutor;
|
||||
import android.annotation.IntDef;
|
||||
import android.annotation.IntRange;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.graphics.RectF;
|
||||
import android.inputmethodservice.InputMethodService;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
@@ -32,7 +35,9 @@ import com.android.internal.util.Preconditions;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.IntConsumer;
|
||||
|
||||
/**
|
||||
@@ -1205,6 +1210,40 @@ public interface InputConnection {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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
|
||||
* method to implement the handwriting gesture API -
|
||||
* {@link #performHandwritingGesture(HandwritingGesture, Executor, IntConsumer)}.
|
||||
*
|
||||
* <p><strong>Editor authors</strong>: It's preferred that the editor returns a
|
||||
* {@link TextBoundsInfo} of all the text lines whose bounds intersect with the given
|
||||
* {@code rectF}.
|
||||
* </p>
|
||||
*
|
||||
* <p><strong>IME authors</strong>: This method is expensive when the text is long. Please
|
||||
* consider that both the text bounds computation and IPC round-trip to send the data are time
|
||||
* consuming. It's preferable to only request text bounds in smaller areas.
|
||||
* </p>
|
||||
*
|
||||
* @param rectF 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
|
||||
* non-null object.
|
||||
*
|
||||
* @see TextBoundsInfo
|
||||
* @see android.view.inputmethod.TextBoundsInfoResult
|
||||
*/
|
||||
default void requestTextBoundsInfo(
|
||||
@NonNull RectF rectF, @NonNull @CallbackExecutor Executor executor,
|
||||
@NonNull Consumer<TextBoundsInfoResult> consumer) {
|
||||
Objects.requireNonNull(executor);
|
||||
Objects.requireNonNull(consumer);
|
||||
executor.execute(() -> consumer.accept(new TextBoundsInfoResult(CODE_UNSUPPORTED)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the system to enable application developers to specify a dedicated thread on which
|
||||
* {@link InputConnection} methods are called back.
|
||||
|
||||
@@ -20,6 +20,7 @@ import android.annotation.CallbackExecutor;
|
||||
import android.annotation.IntRange;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.graphics.RectF;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.view.KeyEvent;
|
||||
@@ -27,6 +28,7 @@ import android.view.KeyEvent;
|
||||
import com.android.internal.util.Preconditions;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.IntConsumer;
|
||||
|
||||
/**
|
||||
@@ -342,6 +344,17 @@ public class InputConnectionWrapper implements InputConnection {
|
||||
return mTarget.requestCursorUpdates(cursorUpdateMode);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* @throws NullPointerException if the target is {@code null}.
|
||||
*/
|
||||
@Override
|
||||
public void requestTextBoundsInfo(
|
||||
@NonNull RectF rectF, @NonNull @CallbackExecutor Executor executor,
|
||||
@NonNull Consumer<TextBoundsInfoResult> consumer) {
|
||||
mTarget.requestTextBoundsInfo(rectF, executor, consumer);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* @throws NullPointerException if the target is {@code null}.
|
||||
|
||||
@@ -28,6 +28,7 @@ import static java.lang.annotation.RetentionPolicy.SOURCE;
|
||||
import android.annotation.AnyThread;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.graphics.RectF;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
@@ -1092,6 +1093,36 @@ final class RemoteInputConnectionImpl extends IRemoteInputConnection.Stub {
|
||||
}
|
||||
}
|
||||
|
||||
@Dispatching(cancellable = true)
|
||||
@Override
|
||||
public void requestTextBoundsInfo(
|
||||
InputConnectionCommandHeader header, RectF rectF,
|
||||
@NonNull ResultReceiver resultReceiver) {
|
||||
dispatchWithTracing("requestTextBoundsInfo", () -> {
|
||||
if (header.mSessionId != mCurrentSessionId.get()) {
|
||||
resultReceiver.send(TextBoundsInfoResult.CODE_CANCELLED, null);
|
||||
return; // cancelled
|
||||
}
|
||||
InputConnection ic = getInputConnection();
|
||||
if (ic == null || !isActive()) {
|
||||
Log.w(TAG, "requestTextBoundsInfo on inactive InputConnection");
|
||||
resultReceiver.send(TextBoundsInfoResult.CODE_CANCELLED, null);
|
||||
return;
|
||||
}
|
||||
|
||||
ic.requestTextBoundsInfo(
|
||||
rectF,
|
||||
Runnable::run,
|
||||
(textBoundsInfoResult) -> {
|
||||
final int resultCode = textBoundsInfoResult.getResultCode();
|
||||
final TextBoundsInfo textBoundsInfo =
|
||||
textBoundsInfoResult.getTextBoundsInfo();
|
||||
resultReceiver.send(resultCode,
|
||||
textBoundsInfo == null ? null : textBoundsInfo.toBundle());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@Dispatching(cancellable = true)
|
||||
@Override
|
||||
public void commitContent(InputConnectionCommandHeader header,
|
||||
|
||||
844
core/java/android/view/inputmethod/TextBoundsInfo.java
Normal file
844
core/java/android/view/inputmethod/TextBoundsInfo.java
Normal file
@@ -0,0 +1,844 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.view.inputmethod;
|
||||
|
||||
import android.annotation.IntDef;
|
||||
import android.annotation.IntRange;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.RectF;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.text.SegmentFinder;
|
||||
|
||||
import com.android.internal.util.ArrayUtils;
|
||||
import com.android.internal.util.GrowingArrayUtils;
|
||||
import com.android.internal.util.Preconditions;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* The text bounds information of a slice of text in the editor.
|
||||
*
|
||||
* <p> 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.
|
||||
* </p>
|
||||
*/
|
||||
public final class TextBoundsInfo implements Parcelable {
|
||||
/**
|
||||
* The flag indicating that the character is a whitespace.
|
||||
*
|
||||
* @see Builder#setCharacterFlags(int[])
|
||||
* @see #getCharacterFlags(int)
|
||||
*/
|
||||
public static final int FLAG_CHARACTER_WHITESPACE = 1;
|
||||
|
||||
/**
|
||||
* The flag indicating that the character is a linefeed character.
|
||||
*
|
||||
* @see Builder#setCharacterFlags(int[])
|
||||
* @see #getCharacterFlags(int)
|
||||
*/
|
||||
public static final int FLAG_CHARACTER_LINEFEED = 1 << 1;
|
||||
|
||||
/**
|
||||
* The flag indicating that the character is a punctuation.
|
||||
*
|
||||
* @see Builder#setCharacterFlags(int[])
|
||||
* @see #getCharacterFlags(int)
|
||||
*/
|
||||
public static final int FLAG_CHARACTER_PUNCTUATION = 1 << 2;
|
||||
|
||||
/**
|
||||
* The flag indicating that the line this character belongs to has RTL line direction. It's
|
||||
* required that all characters in the same line must have the same direction.
|
||||
*
|
||||
* @see Builder#setCharacterFlags(int[])
|
||||
* @see #getCharacterFlags(int)
|
||||
*/
|
||||
public static final int FLAG_LINE_IS_RTL = 1 << 3;
|
||||
|
||||
|
||||
/** @hide */
|
||||
@IntDef(prefix = "FLAG_", flag = true, value = {
|
||||
FLAG_CHARACTER_WHITESPACE,
|
||||
FLAG_CHARACTER_LINEFEED,
|
||||
FLAG_CHARACTER_PUNCTUATION,
|
||||
FLAG_LINE_IS_RTL
|
||||
})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface CharacterFlags {}
|
||||
|
||||
/** All the valid flags. */
|
||||
private static final int KNOWN_CHARACTER_FLAGS = FLAG_CHARACTER_WHITESPACE
|
||||
| FLAG_CHARACTER_LINEFEED | FLAG_CHARACTER_PUNCTUATION | FLAG_LINE_IS_RTL;
|
||||
|
||||
/**
|
||||
* The amount of shift to get the character's BiDi level from the internal character flags.
|
||||
*/
|
||||
private static final int BIDI_LEVEL_SHIFT = 19;
|
||||
|
||||
/**
|
||||
* The mask used to get the character's BiDi level from the internal character flags.
|
||||
*/
|
||||
private static final int BIDI_LEVEL_MASK = 0x7F << BIDI_LEVEL_SHIFT;
|
||||
|
||||
/**
|
||||
* The flag indicating that the character at the index is the start of a line segment.
|
||||
* This flag is only used internally to serialize the {@link SegmentFinder}.
|
||||
*
|
||||
* @see #writeToParcel(Parcel, int)
|
||||
*/
|
||||
private static final int FLAG_LINE_SEGMENT_START = 1 << 31;
|
||||
|
||||
/**
|
||||
* The flag indicating that the character at the index is the end of a line segment.
|
||||
* This flag is only used internally to serialize the {@link SegmentFinder}.
|
||||
*
|
||||
* @see #writeToParcel(Parcel, int)
|
||||
*/
|
||||
private static final int FLAG_LINE_SEGMENT_END = 1 << 30;
|
||||
|
||||
/**
|
||||
* The flag indicating that the character at the index is the start of a word segment.
|
||||
* This flag is only used internally to serialize the {@link SegmentFinder}.
|
||||
*
|
||||
* @see #writeToParcel(Parcel, int)
|
||||
*/
|
||||
private static final int FLAG_WORD_SEGMENT_START = 1 << 29;
|
||||
|
||||
/**
|
||||
* The flag indicating that the character at the index is the end of a word segment.
|
||||
* This flag is only used internally to serialize the {@link SegmentFinder}.
|
||||
*
|
||||
* @see #writeToParcel(Parcel, int)
|
||||
*/
|
||||
private static final int FLAG_WORD_SEGMENT_END = 1 << 28;
|
||||
|
||||
/**
|
||||
* The flag indicating that the character at the index is the start of a grapheme segment.
|
||||
* It's only used internally to serialize the {@link SegmentFinder}.
|
||||
*
|
||||
* @see #writeToParcel(Parcel, int)
|
||||
*/
|
||||
private static final int FLAG_GRAPHEME_SEGMENT_START = 1 << 27;
|
||||
|
||||
/**
|
||||
* The flag indicating that the character at the index is the end of a grapheme segment.
|
||||
* It's only used internally to serialize the {@link SegmentFinder}.
|
||||
*
|
||||
* @see #writeToParcel(Parcel, int)
|
||||
*/
|
||||
private static final int FLAG_GRAPHEME_SEGMENT_END = 1 << 26;
|
||||
|
||||
private final int mStart;
|
||||
private final int mEnd;
|
||||
private final float[] mMatrixValues;
|
||||
private final float[] mCharacterBounds;
|
||||
/**
|
||||
* The array that encodes character and BiDi levels. They are stored together to save memory
|
||||
* space, and it's easier during serialization.
|
||||
*/
|
||||
private final int[] mInternalCharacterFlags;
|
||||
private final SegmentFinder mLineSegmentFinder;
|
||||
private final SegmentFinder mWordSegmentFinder;
|
||||
private final SegmentFinder mGraphemeSegmentFinder;
|
||||
|
||||
/**
|
||||
* Returns a new instance of {@link android.graphics.Matrix} that indicates 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();
|
||||
matrix.setValues(mMatrixValues);
|
||||
return matrix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the index of the first character whose bounds information is available in this
|
||||
* {@link TextBoundsInfo}, inclusive.
|
||||
*
|
||||
* @see Builder#setStartAndEnd(int, int)
|
||||
*/
|
||||
public int getStart() {
|
||||
return mStart;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the index of the last character whose bounds information is available in this
|
||||
* {@link TextBoundsInfo}, exclusive.
|
||||
*
|
||||
* @see Builder#setStartAndEnd(int, int)
|
||||
*/
|
||||
public int getEnd() {
|
||||
return mEnd;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the bounds of the character at the given {@code index}, in the coordinates of the
|
||||
* editor.
|
||||
*
|
||||
* @param index the index of the queried character.
|
||||
* @return the bounding box of the queried character.
|
||||
*
|
||||
* @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) {
|
||||
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],
|
||||
mCharacterBounds[offset + 2], mCharacterBounds[offset + 3]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the flags associated with the character at the given {@code index}.
|
||||
* The flags contain the following information:
|
||||
* <ul>
|
||||
* <li>The {@link #FLAG_CHARACTER_WHITESPACE} flag, indicating the character is a
|
||||
* whitespace. </li>
|
||||
* <li>The {@link #FLAG_CHARACTER_LINEFEED} flag, indicating the character is a
|
||||
* linefeed. </li>
|
||||
* <li>The {@link #FLAG_CHARACTER_PUNCTUATION} flag, indicating the character is a
|
||||
* punctuation. </li>
|
||||
* <li>The {@link #FLAG_LINE_IS_RTL} flag, indicating the line this character belongs to
|
||||
* has RTL line direction. All characters in the same line must have the same line
|
||||
* direction. Check {@link #getLineSegmentFinder()} for more information of
|
||||
* line boundaries. </li>
|
||||
* </ul>
|
||||
*
|
||||
* @param index the index of the queried character.
|
||||
* @return the flags associated with the queried character.
|
||||
*
|
||||
* @throws IndexOutOfBoundsException if the given {@code index} is out of the range from
|
||||
* the {@code start} to the {@code end}.
|
||||
*
|
||||
* @see #FLAG_CHARACTER_WHITESPACE
|
||||
* @see #FLAG_CHARACTER_LINEFEED
|
||||
* @see #FLAG_CHARACTER_PUNCTUATION
|
||||
* @see #FLAG_LINE_IS_RTL
|
||||
*/
|
||||
@CharacterFlags
|
||||
public int getCharacterFlags(int index) {
|
||||
if (index < mStart || index >= mEnd) {
|
||||
throw new IndexOutOfBoundsException("Index is out of the bounds of "
|
||||
+ "[" + mStart + ", " + mEnd + ").");
|
||||
}
|
||||
final int offset = index - mStart;
|
||||
return mInternalCharacterFlags[offset] & KNOWN_CHARACTER_FLAGS;
|
||||
}
|
||||
|
||||
/**
|
||||
* The BiDi level of the character at the given {@code index}. <br/>
|
||||
* BiDi level is defined by
|
||||
* <a href="https://unicode.org/reports/tr9/#Basic_Display_Algorithm" >the unicode
|
||||
* bidirectional algorithm </a>. One can determine whether a character's direction is
|
||||
* right-to-left (RTL) or left-to-right (LTR) by checking the last bit of the BiDi level.
|
||||
* If it's 1, the character is RTL, otherwise the character is LTR. The BiDi level of a
|
||||
* character must be in the range of [0, 125].
|
||||
*
|
||||
* @param index the index of the queried character.
|
||||
* @return the BiDi level of the character, which is an integer in the range of [0, 125].
|
||||
* @throws IndexOutOfBoundsException if the given {@code index} is out of the range from
|
||||
* the {@code start} to the {@code end}.
|
||||
*
|
||||
* @see Builder#setCharacterBidiLevel(int[])
|
||||
*/
|
||||
@IntRange(from = 0, to = 125)
|
||||
public int getCharacterBidiLevel(int index) {
|
||||
if (index < mStart || index >= mEnd) {
|
||||
throw new IndexOutOfBoundsException("Index is out of the bounds of "
|
||||
+ "[" + mStart + ", " + mEnd + ").");
|
||||
}
|
||||
final int offset = index - mStart;
|
||||
return (mInternalCharacterFlags[offset] & BIDI_LEVEL_MASK) >> BIDI_LEVEL_SHIFT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link SegmentFinder} that locates the word boundaries.
|
||||
*
|
||||
* @see Builder#setWordSegmentFinder(SegmentFinder)
|
||||
*/
|
||||
@NonNull
|
||||
public SegmentFinder getWordSegmentFinder() {
|
||||
return mWordSegmentFinder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link SegmentFinder} that locates the grapheme boundaries.
|
||||
*
|
||||
* @see Builder#setGraphemeSegmentFinder(SegmentFinder)
|
||||
*/
|
||||
@NonNull
|
||||
public SegmentFinder getGraphemeSegmentFinder() {
|
||||
return mGraphemeSegmentFinder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link SegmentFinder} that locates the line boundaries.
|
||||
*
|
||||
* @see Builder#setLineSegmentFinder(SegmentFinder)
|
||||
*/
|
||||
@NonNull
|
||||
public SegmentFinder getLineSegmentFinder() {
|
||||
return mLineSegmentFinder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Describe the kinds of special objects contained in this Parcelable
|
||||
* instance's marshaled representation. For example, if the object will
|
||||
* include a file descriptor in the output of {@link #writeToParcel(Parcel, int)},
|
||||
* the return value of this method must include the
|
||||
* {@link #CONTENTS_FILE_DESCRIPTOR} bit.
|
||||
*
|
||||
* @return a bitmask indicating the set of special object types marshaled
|
||||
* by this Parcelable object instance.
|
||||
*/
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Flatten this object in to a Parcel.
|
||||
*
|
||||
* @param dest The Parcel in which the object should be written.
|
||||
* @param flags Additional flags about how the object should be written.
|
||||
* May be 0 or {@link #PARCELABLE_WRITE_RETURN_VALUE}.
|
||||
*/
|
||||
@Override
|
||||
public void writeToParcel(@NonNull Parcel dest, int flags) {
|
||||
dest.writeInt(mStart);
|
||||
dest.writeInt(mEnd);
|
||||
dest.writeFloatArray(mMatrixValues);
|
||||
dest.writeFloatArray(mCharacterBounds);
|
||||
|
||||
// The end can also be a break position. We need an extra space to encode the breaks.
|
||||
final int[] encodedFlags = Arrays.copyOf(mInternalCharacterFlags, mEnd - mStart + 1);
|
||||
encodeSegmentFinder(encodedFlags, FLAG_GRAPHEME_SEGMENT_START, FLAG_GRAPHEME_SEGMENT_END,
|
||||
mStart, mEnd, mGraphemeSegmentFinder);
|
||||
encodeSegmentFinder(encodedFlags, FLAG_WORD_SEGMENT_START, FLAG_WORD_SEGMENT_END, mStart,
|
||||
mEnd, mWordSegmentFinder);
|
||||
encodeSegmentFinder(encodedFlags, FLAG_LINE_SEGMENT_START, FLAG_LINE_SEGMENT_END, mStart,
|
||||
mEnd, mLineSegmentFinder);
|
||||
dest.writeIntArray(encodedFlags);
|
||||
}
|
||||
|
||||
private TextBoundsInfo(Parcel source) {
|
||||
mStart = source.readInt();
|
||||
mEnd = source.readInt();
|
||||
mMatrixValues = Objects.requireNonNull(source.createFloatArray());
|
||||
mCharacterBounds = Objects.requireNonNull(source.createFloatArray());
|
||||
final int[] encodedFlags = Objects.requireNonNull(source.createIntArray());
|
||||
|
||||
mGraphemeSegmentFinder = decodeSegmentFinder(encodedFlags, FLAG_GRAPHEME_SEGMENT_START,
|
||||
FLAG_GRAPHEME_SEGMENT_END, mStart, mEnd);
|
||||
mWordSegmentFinder = decodeSegmentFinder(encodedFlags, FLAG_WORD_SEGMENT_START,
|
||||
FLAG_WORD_SEGMENT_END, mStart, mEnd);
|
||||
mLineSegmentFinder = decodeSegmentFinder(encodedFlags, FLAG_LINE_SEGMENT_START,
|
||||
FLAG_LINE_SEGMENT_END, mStart, mEnd);
|
||||
|
||||
final int length = mEnd - mStart;
|
||||
final int flagsMask = KNOWN_CHARACTER_FLAGS | BIDI_LEVEL_MASK;
|
||||
mInternalCharacterFlags = new int[length];
|
||||
for (int i = 0; i < length; ++i) {
|
||||
// Remove the flags used to encoded segment boundaries.
|
||||
mInternalCharacterFlags[i] = encodedFlags[i] & flagsMask;
|
||||
}
|
||||
}
|
||||
|
||||
private TextBoundsInfo(Builder builder) {
|
||||
mStart = builder.mStart;
|
||||
mEnd = builder.mEnd;
|
||||
mMatrixValues = Arrays.copyOf(builder.mMatrixValues, 9);
|
||||
final int length = mEnd - mStart;
|
||||
mCharacterBounds = Arrays.copyOf(builder.mCharacterBounds, 4 * length);
|
||||
// Store characterFlags and characterBidiLevels to save memory.
|
||||
mInternalCharacterFlags = new int[length];
|
||||
for (int index = 0; index < length; ++index) {
|
||||
mInternalCharacterFlags[index] = builder.mCharacterFlags[index]
|
||||
| (builder.mCharacterBidiLevels[index] << BIDI_LEVEL_SHIFT);
|
||||
}
|
||||
mGraphemeSegmentFinder = builder.mGraphemeSegmentFinder;
|
||||
mWordSegmentFinder = builder.mWordSegmentFinder;
|
||||
mLineSegmentFinder = builder.mLineSegmentFinder;
|
||||
}
|
||||
|
||||
/**
|
||||
* The CREATOR to make this class Parcelable.
|
||||
*/
|
||||
@NonNull
|
||||
public static final Parcelable.Creator<TextBoundsInfo> CREATOR = new Creator<TextBoundsInfo>() {
|
||||
@Override
|
||||
public TextBoundsInfo createFromParcel(Parcel source) {
|
||||
return new TextBoundsInfo(source);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextBoundsInfo[] newArray(int size) {
|
||||
return new TextBoundsInfo[size];
|
||||
}
|
||||
};
|
||||
|
||||
private static final String TEXT_BOUNDS_INFO_KEY = "android.view.inputmethod.TextBoundsInfo";
|
||||
|
||||
/**
|
||||
* Store the {@link TextBoundsInfo} into a {@link Bundle}. This method is used by
|
||||
* {@link RemoteInputConnectionImpl} to transfer the {@link TextBoundsInfo} from the editor
|
||||
* to IME.
|
||||
*
|
||||
* @see TextBoundsInfoResult
|
||||
* @see InputConnection#requestTextBoundsInfo(RectF, Executor, Consumer)
|
||||
* @hide
|
||||
*/
|
||||
@NonNull
|
||||
public Bundle toBundle() {
|
||||
final Bundle bundle = new Bundle();
|
||||
bundle.putParcelable(TEXT_BOUNDS_INFO_KEY, this);
|
||||
return bundle;
|
||||
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
@Nullable
|
||||
public static TextBoundsInfo createFromBundle(@Nullable Bundle bundle) {
|
||||
if (bundle == null) return null;
|
||||
return bundle.getParcelable(TEXT_BOUNDS_INFO_KEY, TextBoundsInfo.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* The builder class to create a {@link TextBoundsInfo} object.
|
||||
*/
|
||||
public static final class Builder {
|
||||
private final float[] mMatrixValues = new float[9];
|
||||
private boolean mMatrixInitialized;
|
||||
private int mStart;
|
||||
private int mEnd;
|
||||
private float[] mCharacterBounds;
|
||||
private int[] mCharacterFlags;
|
||||
private int[] mCharacterBidiLevels;
|
||||
private SegmentFinder mLineSegmentFinder;
|
||||
private SegmentFinder mWordSegmentFinder;
|
||||
private SegmentFinder mGraphemeSegmentFinder;
|
||||
|
||||
/** Clear all the parameters set on this {@link Builder} to reuse it. */
|
||||
@NonNull
|
||||
public Builder clear() {
|
||||
mMatrixInitialized = false;
|
||||
mStart = -1;
|
||||
mEnd = -1;
|
||||
mCharacterBounds = null;
|
||||
mCharacterFlags = null;
|
||||
mLineSegmentFinder = null;
|
||||
mWordSegmentFinder = null;
|
||||
mGraphemeSegmentFinder = null;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the matrix that transforms local coordinates into screen coordinates.
|
||||
*
|
||||
* @param matrix transformation matrix from local coordinates into screen coordinates.
|
||||
* @throws NullPointerException if the given {@code matrix} is {@code null}.
|
||||
*/
|
||||
@NonNull
|
||||
public Builder setMatrix(@NonNull Matrix matrix) {
|
||||
Objects.requireNonNull(matrix).getValues(mMatrixValues);
|
||||
mMatrixInitialized = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the start and end index of the {@link TextBoundsInfo}. It's the range of the
|
||||
* characters whose information is available in the {@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}.
|
||||
*/
|
||||
@NonNull
|
||||
@SuppressWarnings("MissingGetterMatchingBuilder")
|
||||
public Builder setStartAndEnd(@IntRange(from = 0) int start, @IntRange(from = 0) int end) {
|
||||
Preconditions.checkArgument(start >= 0);
|
||||
Preconditions.checkArgumentInRange(start, 0, end, "start");
|
||||
mStart = start;
|
||||
mEnd = end;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the characters bounds, in the coordinates of the editor. <br/>
|
||||
*
|
||||
* The given array should be divided into groups of four where each element represents
|
||||
* left, top, right and bottom of the character bounds respectively.
|
||||
* The bounds of the i-th character in the editor should be stored at index
|
||||
* 4 * (i - start). The length of the given array must equal to 4 * (end - start). <br/>
|
||||
*
|
||||
* Sometimes multiple characters in a single grapheme are rendered as one symbol on the
|
||||
* screen. So those characters only have one shared bounds. In this case, we recommend the
|
||||
* editor to assign all the width to the bounds of the first character in the grapheme,
|
||||
* and make the rest characters' bounds zero-width. <br/>
|
||||
*
|
||||
* For example, the string "'0xD83D' '0xDE00'" is rendered as one grapheme - a grinning face
|
||||
* emoji. If the bounds of the grapheme is: Rect(5, 10, 15, 20), the character bounds of the
|
||||
* string should be: [ Rect(5, 10, 15, 20), Rect(15, 10, 15, 20) ].
|
||||
*
|
||||
* @param characterBounds the array of the flattened character bounds.
|
||||
* @throws NullPointerException if the given {@code characterBounds} is {@code null}.
|
||||
*/
|
||||
@NonNull
|
||||
public Builder setCharacterBounds(@NonNull float[] characterBounds) {
|
||||
mCharacterBounds = Objects.requireNonNull(characterBounds);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the flags of the characters. The flags of the i-th character in the editor is stored
|
||||
* at index (i - start). The length of the given array must equal to (end - start).
|
||||
* The flags contain the following information:
|
||||
* <ul>
|
||||
* <li>The {@link #FLAG_CHARACTER_WHITESPACE} flag, indicating the character is a
|
||||
* whitespace. </li>
|
||||
* <li>The {@link #FLAG_CHARACTER_LINEFEED} flag, indicating the character is a
|
||||
* linefeed. </li>
|
||||
* <li>The {@link #FLAG_CHARACTER_PUNCTUATION} flag, indicating the character is a
|
||||
* punctuation. </li>
|
||||
* <li>The {@link #FLAG_LINE_IS_RTL} flag, indicating the line this character belongs to
|
||||
* is RTL. All all character in the same line must have the same line direction. Check
|
||||
* {@link #getLineSegmentFinder()} for more information of line boundaries. </li>
|
||||
* </ul>
|
||||
*
|
||||
* @param characterFlags the array of the character's flags.
|
||||
* @throws NullPointerException if the given {@code characterFlags} is {@code null}.
|
||||
* @throws IllegalArgumentException if the given {@code characterFlags} contains invalid
|
||||
* flags.
|
||||
*
|
||||
* @see #getCharacterFlags(int)
|
||||
*/
|
||||
@NonNull
|
||||
public Builder setCharacterFlags(@NonNull int[] characterFlags) {
|
||||
Objects.requireNonNull(characterFlags);
|
||||
for (int characterFlag : characterFlags) {
|
||||
if ((characterFlag & (~KNOWN_CHARACTER_FLAGS)) != 0) {
|
||||
throw new IllegalArgumentException("characterFlags contains invalid flags.");
|
||||
}
|
||||
}
|
||||
mCharacterFlags = characterFlags;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the BiDi levels for the character. The bidiLevel of the i-th character in the editor
|
||||
* is stored at index (i - start). The length of the given array must equal to
|
||||
* (end - start). <br/>
|
||||
*
|
||||
* BiDi level is defined by
|
||||
* <a href="https://unicode.org/reports/tr9/#Basic_Display_Algorithm" >the unicode
|
||||
* bidirectional algorithm </a>. One can determine whether a character's direction is
|
||||
* right-to-left (RTL) or left-to-right (LTR) by checking the last bit of the BiDi level.
|
||||
* If it's 1, the character is RTL, otherwise the character is LTR. The BiDi level of a
|
||||
* character must be in the range of [0, 125].
|
||||
* @param characterBidiLevels the array of the character's BiDi level.
|
||||
*
|
||||
* @throws NullPointerException if the given {@code characterBidiLevels} is {@code null}.
|
||||
* @throws IllegalArgumentException if the given {@code characterBidiLevels} contains an
|
||||
* element that's out of the range [0, 125].
|
||||
*
|
||||
* @see #getCharacterBidiLevel(int)
|
||||
*/
|
||||
@NonNull
|
||||
public Builder setCharacterBidiLevel(@NonNull int[] characterBidiLevels) {
|
||||
Objects.requireNonNull(characterBidiLevels);
|
||||
for (int index = 0; index < characterBidiLevels.length; ++index) {
|
||||
Preconditions.checkArgumentInRange(characterBidiLevels[index], 0, 125,
|
||||
"bidiLevels[" + index + "]");
|
||||
}
|
||||
mCharacterBidiLevels = characterBidiLevels;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link SegmentFinder} that locates the grapheme cluster boundaries. Grapheme is
|
||||
* defined in <a href="https://unicode.org/reports/tr29/#Grapheme_Cluster_Boundaries">
|
||||
* the unicode annex #29: unicode text segmentation<a/>. It's a user-perspective character.
|
||||
* And it's usually the minimal unit for selection, backspace, deletion etc. <br/>
|
||||
*
|
||||
* Please note that only the grapheme segments within the range from start to end will
|
||||
* be available to the IME. The remaining information will be discarded during serialization
|
||||
* for better performance.
|
||||
*
|
||||
* @param graphemeSegmentFinder the {@link SegmentFinder} that locates the grapheme cluster
|
||||
* boundaries.
|
||||
* @throws NullPointerException if the given {@code graphemeSegmentFinder} is {@code null}.
|
||||
*
|
||||
* @see #getGraphemeSegmentFinder()
|
||||
* @see SegmentFinder
|
||||
* @see SegmentFinder.DefaultSegmentFinder
|
||||
*/
|
||||
@NonNull
|
||||
public Builder setGraphemeSegmentFinder(@NonNull SegmentFinder graphemeSegmentFinder) {
|
||||
mGraphemeSegmentFinder = Objects.requireNonNull(graphemeSegmentFinder);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link SegmentFinder} that locates the word boundaries. <br/>
|
||||
*
|
||||
* Please note that only the word segments within the range from start to end will
|
||||
* be available to the IME. The remaining information will be discarded during serialization
|
||||
* for better performance.
|
||||
* @param wordSegmentFinder set the {@link SegmentFinder} that locates the word boundaries.
|
||||
* @throws NullPointerException if the given {@code wordSegmentFinder} is {@code null}.
|
||||
*
|
||||
* @see #getWordSegmentFinder()
|
||||
* @see SegmentFinder
|
||||
* @see SegmentFinder.DefaultSegmentFinder
|
||||
*/
|
||||
@NonNull
|
||||
public Builder setWordSegmentFinder(@NonNull SegmentFinder wordSegmentFinder) {
|
||||
mWordSegmentFinder = Objects.requireNonNull(wordSegmentFinder);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link SegmentFinder} that locates the line boundaries. Aside from the hard
|
||||
* breaks in the text, it should also locate the soft line breaks added by the editor.
|
||||
* It is expected that the characters within the same line is rendered on the same baseline.
|
||||
* (Except for some text formatted as subscript and superscript.) <br/>
|
||||
*
|
||||
* Please note that only the line segments within the range from start to end will
|
||||
* be available to the IME. The remaining information will be discarded during serialization
|
||||
* for better performance.
|
||||
* @param lineSegmentFinder set the {@link SegmentFinder} that locates the line boundaries.
|
||||
* @throws NullPointerException if the given {@code lineSegmentFinder} is {@code null}.
|
||||
*
|
||||
* @see #getLineSegmentFinder()
|
||||
* @see SegmentFinder
|
||||
* @see SegmentFinder.DefaultSegmentFinder
|
||||
*/
|
||||
@NonNull
|
||||
public Builder setLineSegmentFinder(@NonNull SegmentFinder lineSegmentFinder) {
|
||||
mLineSegmentFinder = Objects.requireNonNull(lineSegmentFinder);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the {@link TextBoundsInfo} using the parameters in this {@link Builder}.
|
||||
*
|
||||
* @throws IllegalStateException in the following conditions:
|
||||
* <ul>
|
||||
* <li>if the {@code start} or {@code end} is not set.</li>
|
||||
* <li>if the {@code matrix} is not set.</li>
|
||||
* <li>if {@code characterBounds} is not set or its length doesn't equal to
|
||||
* 4 * ({@code end} - {@code start}).</li>
|
||||
* <li>if the {@code characterFlags} is not set or its length doesn't equal to
|
||||
* ({@code end} - {@code start}).</li>
|
||||
* <li>if {@code graphemeSegmentFinder}, {@code wordSegmentFinder} or
|
||||
* {@code lineSegmentFinder} is not set.</li>
|
||||
* <li>if characters in the same line has inconsistent {@link #FLAG_LINE_IS_RTL}
|
||||
* flag.</li>
|
||||
* </ul>
|
||||
*/
|
||||
@NonNull
|
||||
public TextBoundsInfo build() {
|
||||
if (mStart < 0 || mEnd < 0) {
|
||||
throw new IllegalStateException("Start and end must be set.");
|
||||
}
|
||||
|
||||
if (!mMatrixInitialized) {
|
||||
throw new IllegalStateException("Matrix must be set.");
|
||||
}
|
||||
|
||||
if (mCharacterBounds == null) {
|
||||
throw new IllegalStateException("CharacterBounds must be set.");
|
||||
}
|
||||
|
||||
if (mCharacterFlags == null) {
|
||||
throw new IllegalStateException("CharacterFlags must be set.");
|
||||
}
|
||||
|
||||
if (mCharacterBidiLevels == null) {
|
||||
throw new IllegalStateException("CharacterBidiLevel must be set.");
|
||||
}
|
||||
|
||||
if (mCharacterBounds.length != 4 * (mEnd - mStart)) {
|
||||
throw new IllegalStateException("The length of characterBounds doesn't match the "
|
||||
+ "length of the given start and end."
|
||||
+ " Expected length: " + (4 * (mEnd - mStart))
|
||||
+ " characterBounds length: " + mCharacterBounds.length);
|
||||
}
|
||||
if (mCharacterFlags.length != mEnd - mStart) {
|
||||
throw new IllegalStateException("The length of characterFlags doesn't match the "
|
||||
+ "length of the given start and end."
|
||||
+ " Expected length: " + (mEnd - mStart)
|
||||
+ " characterFlags length: " + mCharacterFlags.length);
|
||||
}
|
||||
if (mCharacterBidiLevels.length != mEnd - mStart) {
|
||||
throw new IllegalStateException("The length of characterBidiLevels doesn't match"
|
||||
+ " the length of the given start and end."
|
||||
+ " Expected length: " + (mEnd - mStart)
|
||||
+ " characterFlags length: " + mCharacterBidiLevels.length);
|
||||
}
|
||||
if (mGraphemeSegmentFinder == null) {
|
||||
throw new IllegalStateException("GraphemeSegmentFinder must be set.");
|
||||
}
|
||||
if (mWordSegmentFinder == null) {
|
||||
throw new IllegalStateException("WordSegmentFinder must be set.");
|
||||
}
|
||||
if (mLineSegmentFinder == null) {
|
||||
throw new IllegalStateException("LineSegmentFinder must be set.");
|
||||
}
|
||||
|
||||
if (!isLineDirectionFlagConsistent(mCharacterFlags, mLineSegmentFinder, mStart, mEnd)) {
|
||||
throw new IllegalStateException("characters in the same line must have the same "
|
||||
+ "FLAG_LINE_IS_RTL flag value.");
|
||||
}
|
||||
return new TextBoundsInfo(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode the segment start and end positions in {@link SegmentFinder} to a flags array.
|
||||
*
|
||||
* For example:
|
||||
* Text: "A BC DE"
|
||||
* Input:
|
||||
* start: 2, end: 7 // substring "BC DE"
|
||||
* SegmentFinder: segment ranges = [(2, 4), (5, 7)] // a word break iterator
|
||||
* flags: [0x0000, 0x0000, 0x0080, 0x0000, 0x0000, 0x0000] // 0x0080 is whitespace
|
||||
* segmentStartFlag: 0x0100
|
||||
* segmentEndFlag: 0x0200
|
||||
* Output:
|
||||
* flags: [0x0100, 0x0000, 0x0280, 0x0100, 0x0000, 0x0200]
|
||||
* The index 2 and 5 encode segment starts, the index 4 and 7 encode a segment end.
|
||||
*
|
||||
* @param flags the flags array to receive the results.
|
||||
* @param segmentStartFlag the flag used to encode the segment start.
|
||||
* @param segmentEndFlag the flag used to encode the segment end.
|
||||
* @param start the start index of the encoded range, inclusive.
|
||||
* @param end the end index of the encoded range, inclusive.
|
||||
* @param segmentFinder the SegmentFinder to be encoded.
|
||||
*
|
||||
* @see #decodeSegmentFinder(int[], int, int, int, int)
|
||||
*/
|
||||
private static void encodeSegmentFinder(@NonNull int[] flags, int segmentStartFlag,
|
||||
int segmentEndFlag, int start, int end, @NonNull SegmentFinder segmentFinder) {
|
||||
if (end - start + 1 != flags.length) {
|
||||
throw new IllegalStateException("The given flags array must have the same length as"
|
||||
+ " the given range. flags length: " + flags.length
|
||||
+ " range: [" + start + ", " + end + "]");
|
||||
}
|
||||
|
||||
int segmentEnd = segmentFinder.nextEndBoundary(start);
|
||||
if (segmentEnd == SegmentFinder.DONE) return;
|
||||
int segmentStart = segmentFinder.previousStartBoundary(segmentEnd);
|
||||
|
||||
while (segmentEnd != SegmentFinder.DONE && segmentEnd <= end) {
|
||||
if (segmentStart >= start) {
|
||||
flags[segmentStart - start] |= segmentStartFlag;
|
||||
flags[segmentEnd - start] |= segmentEndFlag;
|
||||
}
|
||||
segmentStart = segmentFinder.nextStartBoundary(segmentStart);
|
||||
segmentEnd = segmentFinder.nextEndBoundary(segmentEnd);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode a {@link SegmentFinder} from a flags array.
|
||||
*
|
||||
* For example:
|
||||
* Text: "A BC DE"
|
||||
* Input:
|
||||
* start: 2, end: 7 // substring "BC DE"
|
||||
* flags: [0x0100, 0x0000, 0x0280, 0x0100, 0x0000, 0x0200]
|
||||
* segmentStartFlag: 0x0100
|
||||
* segmentEndFlag: 0x0200
|
||||
* Output:
|
||||
* SegmentFinder: segment ranges = [(2, 4), (5, 7)]
|
||||
*
|
||||
* @param flags the flags array to decode the SegmentFinder.
|
||||
* @param segmentStartFlag the flag to decode a segment start.
|
||||
* @param segmentEndFlag the flag to decode a segment end.
|
||||
* @param start the start index of the interested range, inclusive.
|
||||
* @param end the end index of the interested range, inclusive.
|
||||
*
|
||||
* @see #encodeSegmentFinder(int[], int, int, int, int, SegmentFinder)
|
||||
*/
|
||||
private static SegmentFinder decodeSegmentFinder(int[] flags, int segmentStartFlag,
|
||||
int segmentEndFlag, int start, int end) {
|
||||
if (end - start + 1 != flags.length) {
|
||||
throw new IllegalStateException("The given flags array must have the same length as"
|
||||
+ " the given range. flags length: " + flags.length
|
||||
+ " range: [" + start + ", " + end + "]");
|
||||
}
|
||||
int[] breaks = ArrayUtils.newUnpaddedIntArray(10);
|
||||
int count = 0;
|
||||
for (int offset = 0; offset < flags.length; ++offset) {
|
||||
if ((flags[offset] & segmentStartFlag) == segmentStartFlag) {
|
||||
breaks = GrowingArrayUtils.append(breaks, count++, start + offset);
|
||||
}
|
||||
if ((flags[offset] & segmentEndFlag) == segmentEndFlag) {
|
||||
breaks = GrowingArrayUtils.append(breaks, count++, start + offset);
|
||||
}
|
||||
}
|
||||
return new SegmentFinder.DefaultSegmentFinder(Arrays.copyOf(breaks, count));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the {@link #FLAG_LINE_IS_RTL} is the same for characters in the same line.
|
||||
* @return true if all characters in the same line has the same {@link #FLAG_LINE_IS_RTL} flag.
|
||||
*/
|
||||
private static boolean isLineDirectionFlagConsistent(int[] characterFlags,
|
||||
SegmentFinder lineSegmentFinder, int start, int end) {
|
||||
int segmentEnd = lineSegmentFinder.nextEndBoundary(start);
|
||||
if (segmentEnd == SegmentFinder.DONE) return true;
|
||||
int segmentStart = lineSegmentFinder.previousStartBoundary(segmentEnd);
|
||||
|
||||
while (segmentStart != SegmentFinder.DONE && segmentStart < end) {
|
||||
final int lineStart = Math.max(segmentStart, start);
|
||||
final int lineEnd = Math.min(segmentEnd, end);
|
||||
final boolean lineIsRtl = (characterFlags[lineStart - start] & FLAG_LINE_IS_RTL) != 0;
|
||||
for (int index = lineStart + 1; index < lineEnd; ++index) {
|
||||
final int flags = characterFlags[index - start];
|
||||
final boolean characterLineIsRtl = (flags & FLAG_LINE_IS_RTL) != 0;
|
||||
if (characterLineIsRtl != lineIsRtl) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
segmentStart = lineSegmentFinder.nextStartBoundary(segmentStart);
|
||||
segmentEnd = lineSegmentFinder.nextEndBoundary(segmentEnd);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
137
core/java/android/view/inputmethod/TextBoundsInfoResult.java
Normal file
137
core/java/android/view/inputmethod/TextBoundsInfoResult.java
Normal file
@@ -0,0 +1,137 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.view.inputmethod;
|
||||
|
||||
import android.annotation.IntDef;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.graphics.RectF;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* The object that holds the result of the
|
||||
* {@link InputConnection#requestTextBoundsInfo(RectF, Executor, Consumer)} call.
|
||||
*
|
||||
* @see InputConnection#requestTextBoundsInfo(RectF, Executor, Consumer)
|
||||
*/
|
||||
public final class TextBoundsInfoResult {
|
||||
private final int mResultCode;
|
||||
private final TextBoundsInfo mTextBoundsInfo;
|
||||
|
||||
/**
|
||||
* Result for {@link InputConnection#requestTextBoundsInfo(RectF, Executor, Consumer)} when the
|
||||
* editor doesn't implement the method.
|
||||
*/
|
||||
public static final int CODE_UNSUPPORTED = 0;
|
||||
|
||||
/**
|
||||
* Result for {@link InputConnection#requestTextBoundsInfo(RectF, Executor, Consumer)} when the
|
||||
* editor successfully returns a {@link TextBoundsInfo}.
|
||||
*/
|
||||
public static final int CODE_SUCCESS = 1;
|
||||
|
||||
/**
|
||||
* Result for {@link InputConnection#requestTextBoundsInfo(RectF, Executor, Consumer)} when the
|
||||
* request failed. This result code is returned when the editor can't provide a valid
|
||||
* {@link TextBoundsInfo}. (e.g. The editor view is not laid out.)
|
||||
*/
|
||||
public static final int CODE_FAILED = 2;
|
||||
|
||||
/**
|
||||
* Result for {@link InputConnection#requestTextBoundsInfo(RectF, Executor, Consumer)} when the
|
||||
* request is cancelled. This happens when the {@link InputConnection} is or becomes
|
||||
* invalidated while requesting the
|
||||
* {@link TextBoundsInfo}, for example because a new {@code InputConnection} was started, or
|
||||
* due to {@link InputMethodManager#invalidateInput}.
|
||||
*/
|
||||
public static final int CODE_CANCELLED = 3;
|
||||
|
||||
/** @hide */
|
||||
@IntDef(prefix = { "CODE_" }, value = {
|
||||
CODE_UNSUPPORTED,
|
||||
CODE_SUCCESS,
|
||||
CODE_FAILED,
|
||||
CODE_CANCELLED,
|
||||
})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface ResultCode {}
|
||||
|
||||
/**
|
||||
* Create a {@link TextBoundsInfoResult} object with no {@link TextBoundsInfo}.
|
||||
* The given {@code resultCode} can't be {@link #CODE_SUCCESS}.
|
||||
* @param resultCode the result code of the
|
||||
* {@link InputConnection#requestTextBoundsInfo(RectF, Executor, Consumer)} call.
|
||||
*/
|
||||
public TextBoundsInfoResult(@ResultCode int resultCode) {
|
||||
this(resultCode, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link TextBoundsInfoResult} object.
|
||||
*
|
||||
* @param resultCode the result code of the
|
||||
* {@link InputConnection#requestTextBoundsInfo(RectF, Executor, Consumer)} call.
|
||||
* @param textBoundsInfo the returned {@link TextBoundsInfo} of the
|
||||
* {@link InputConnection#requestTextBoundsInfo(RectF, Executor, Consumer)} call. It can't be
|
||||
* null if the {@code resultCode} is {@link #CODE_SUCCESS}.
|
||||
*
|
||||
* @throws IllegalStateException if the resultCode is
|
||||
* {@link #CODE_SUCCESS} but the given {@code textBoundsInfo}
|
||||
* is null.
|
||||
*/
|
||||
public TextBoundsInfoResult(@ResultCode int resultCode,
|
||||
@NonNull TextBoundsInfo textBoundsInfo) {
|
||||
if (resultCode == CODE_SUCCESS && textBoundsInfo == null) {
|
||||
throw new IllegalStateException("TextBoundsInfo must be provided when the resultCode "
|
||||
+ "is CODE_SUCCESS.");
|
||||
}
|
||||
mResultCode = resultCode;
|
||||
mTextBoundsInfo = textBoundsInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the result code of the
|
||||
* {@link InputConnection#requestTextBoundsInfo(RectF, Executor, Consumer)} call.
|
||||
* Its value is one of the {@link #CODE_UNSUPPORTED}, {@link #CODE_SUCCESS},
|
||||
* {@link #CODE_FAILED} and {@link #CODE_CANCELLED}.
|
||||
*/
|
||||
@ResultCode
|
||||
public int getResultCode() {
|
||||
return mResultCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@link TextBoundsInfo} provided by the editor. It is non-null if the
|
||||
* {@code resultCode} is {@link #CODE_SUCCESS}.
|
||||
* Otherwise, it can be null in the following conditions:
|
||||
* <ul>
|
||||
* <li>the editor doesn't support
|
||||
* {@link InputConnection#requestTextBoundsInfo(RectF, Executor, Consumer)}.</li>
|
||||
* <li>the editor doesn't have the text bounds information at the moment. (e.g. the editor
|
||||
* view is not laid out yet.) </li>
|
||||
* <li> the {@link InputConnection} is or become inactive during the request. </li>
|
||||
* <ul/>
|
||||
*/
|
||||
@Nullable
|
||||
public TextBoundsInfo getTextBoundsInfo() {
|
||||
return mTextBoundsInfo;
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package com.android.internal.inputmethod;
|
||||
|
||||
import android.graphics.RectF;
|
||||
import android.os.Bundle;
|
||||
import android.os.ResultReceiver;
|
||||
import android.view.KeyEvent;
|
||||
@@ -106,6 +107,9 @@ 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,
|
||||
in ResultReceiver resultReceiver /* T=TextBoundsInfoResult */);
|
||||
|
||||
void commitContent(in InputConnectionCommandHeader header, in InputContentInfo inputContentInfo,
|
||||
int flags, in Bundle opts, in AndroidFuture future /* T=Boolean */);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user