From 02b50d49442e074fec2c5db3a3951ef2218b769a Mon Sep 17 00:00:00 2001
From: Lan Wei
This method may fail either if the input connection has become invalid (such as its + * process crashing), or the client is taking too long to respond with the text (it is given a + * couple seconds to return), or the protocol is not supported. In any of these cases, null is + * returned. + * + *
This method does not affect the text in the editor in any way, nor does it affect the + * selection or composing spans.
+ * + *If {@link #GET_TEXT_WITH_STYLES} is supplied as flags, the editor should return a + * {@link android.text.Spanned} with all the spans set on the text.
+ * + *IME authors: please consider this will trigger an IPC round-trip that + * will take some time. Assume this method consumes a lot of time. If you are using this to get + * the initial surrounding text around the cursor, you may consider using + * {@link EditorInfo#getInitialTextBeforeCursor(int, int)}, + * {@link EditorInfo#getInitialSelectedText(int)}, and + * {@link EditorInfo#getInitialTextAfterCursor(int, int)} to prevent IPC costs.
+ * + * @param beforeLength The expected length of the text before the cursor. + * @param afterLength The expected length of the text after the cursor. + * @param flags Supplies additional options controlling how the text is returned. Defined by the + * constants. + * @return an {@link android.view.inputmethod.SurroundingText} object describing the surrounding + * text and state of selection, or null if the input connection is no longer valid, or the + * editor can't comply with the request for some reason, or the application does not implement + * this method. The length of the returned text might be less than the sum of + * beforeLength and afterLength . + */ + @Nullable + default SurroundingText getSurroundingText( + @IntRange(from = 0) int beforeLength, @IntRange(from = 0) int afterLength, + @GetTextType int flags) { + CharSequence textBeforeCursor = getTextBeforeCursor(beforeLength, flags); + if (textBeforeCursor == null) { + textBeforeCursor = ""; + } + CharSequence selectedText = getSelectedText(flags); + if (selectedText == null) { + selectedText = ""; + } + CharSequence textAfterCursor = getTextAfterCursor(afterLength, flags); + if (textAfterCursor == null) { + textAfterCursor = ""; + } + CharSequence surroundingText = + TextUtils.concat(textBeforeCursor, selectedText, textAfterCursor); + return new SurroundingText(surroundingText, textBeforeCursor.length(), + textBeforeCursor.length() + selectedText.length(), -1); + } + /** * Retrieve the current capitalization mode in effect at the * current cursor position in the text. See diff --git a/core/java/android/view/inputmethod/InputConnectionInspector.java b/core/java/android/view/inputmethod/InputConnectionInspector.java index 5f25bf58ce57f..7621da7cef1bc 100644 --- a/core/java/android/view/inputmethod/InputConnectionInspector.java +++ b/core/java/android/view/inputmethod/InputConnectionInspector.java @@ -44,6 +44,7 @@ public final class InputConnectionInspector { MissingMethodFlags.GET_HANDLER, MissingMethodFlags.CLOSE_CONNECTION, MissingMethodFlags.COMMIT_CONTENT, + MissingMethodFlags.GET_SURROUNDING_TEXT }) public @interface MissingMethodFlags { /** @@ -86,6 +87,11 @@ public final class InputConnectionInspector { * {@link android.os.Build.VERSION_CODES#N} MR-1 and later. */ int COMMIT_CONTENT = 1 << 7; + /** + * {@link InputConnection#getSurroundingText(int, int, int)} is available in + * {@link android.os.Build.VERSION_CODES#S} and later. + */ + int GET_SURROUNDING_TEXT = 1 << 8; } private static final MapThis contains information about the text and the selection relative to the text.
+ */ +public final class SurroundingText implements Parcelable { + /** + * The surrounding text around the cursor. + */ + @NonNull + private final CharSequence mText; + + /** + * The text offset of the start of the selection in the surrounding text. + * + *This needs to be the position relative to the {@link #mText} instead of the real position + * in the editor.
+ */ + @IntRange(from = 0) + private final int mSelectionStart; + + /** + * The text offset of the end of the selection in the surrounding text. + * + *This needs to be the position relative to the {@link #mText} instead of the real position + * in the editor.
+ */ + @IntRange(from = 0) + private final int mSelectionEnd; + + /** + * The text offset between the start of the editor's text and the start of the surrounding text. + * + *-1 indicates the offset information is unknown.
+ */ + @IntRange(from = -1) + private final int mOffset; + + /** + * Constructor. + * + * @param text The surrounding text. + * @param selectionStart The text offset of the start of the selection in the surrounding text. + * Reversed selection is allowed. + * @param selectionEnd The text offset of the end of the selection in the surrounding text. + * Reversed selection is allowed. + * @param offset The text offset between the start of the editor's text and the start of the + * surrounding text. -1 indicates the offset is unknown. + */ + public SurroundingText(@NonNull final CharSequence text, + @IntRange(from = 0) int selectionStart, @IntRange(from = 0) int selectionEnd, + @IntRange(from = -1) int offset) { + mText = text; + mSelectionStart = selectionStart; + mSelectionEnd = selectionEnd; + mOffset = offset; + } + + /** + * Returns the surrounding text around the cursor. + */ + @NonNull + public CharSequence getText() { + return mText; + } + + /** + * Returns the text offset of the start of the selection in the surrounding text. + */ + @IntRange(from = 0) + public int getSelectionStart() { + return mSelectionStart; + } + + /** + * Returns the text offset of the end of the selection in the surrounding text. + */ + @IntRange(from = 0) + public int getSelectionEnd() { + return mSelectionEnd; + } + + /** + * Returns text offset between the start of the editor's text and the start of the surrounding + * text. + * + *-1 indicates the offset information is unknown.
+ */ + @IntRange(from = -1) + public int getOffset() { + return mOffset; + } + + @Override + public void writeToParcel(@NonNull Parcel out, int flags) { + TextUtils.writeToParcel(mText, out, flags); + out.writeInt(mSelectionStart); + out.writeInt(mSelectionEnd); + out.writeInt(mOffset); + } + + @Override + public int describeContents() { + return 0; + } + + @NonNull + public static final Parcelable.CreatorSee {@link InputConnection#getSurroundingText(int, int, int)}.
+ */
+ public void getSurroundingText(int beforeLength, int afterLength, int flags,
+ ISurroundingTextResultCallback callback) {
+ final SomeArgs args = SomeArgs.obtain();
+ args.arg1 = beforeLength;
+ args.arg2 = afterLength;
+ args.arg3 = flags;
+ args.arg4 = callback;
+ dispatchMessage(mH.obtainMessage(DO_GET_SURROUNDING_TEXT, flags, 0 /* unused */, args));
+ }
+
public void getCursorCapsMode(int reqModes, IIntResultCallback callback) {
dispatchMessage(
mH.obtainMessage(DO_GET_CURSOR_CAPS_MODE, reqModes, 0 /* unused */, callback));
@@ -293,6 +312,33 @@ public abstract class IInputConnectionWrapper extends IInputContext.Stub {
}
return;
}
+ case DO_GET_SURROUNDING_TEXT: {
+ final SomeArgs args = (SomeArgs) msg.obj;
+ try {
+ int beforeLength = (int) args.arg1;
+ int afterLength = (int) args.arg2;
+ int flags = (int) args.arg3;
+ final ISurroundingTextResultCallback callback =
+ (ISurroundingTextResultCallback) args.arg4;
+ final InputConnection ic = getInputConnection();
+ final SurroundingText result;
+ if (ic == null || !isActive()) {
+ Log.w(TAG, "getSurroundingText on inactive InputConnection");
+ result = null;
+ } else {
+ result = ic.getSurroundingText(beforeLength, afterLength, flags);
+ }
+ try {
+ callback.onResult(result);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to return the result to getSurroundingText()."
+ + " result=" + result, e);
+ }
+ } finally {
+ args.recycle();
+ }
+ return;
+ }
case DO_GET_CURSOR_CAPS_MODE: {
final IIntResultCallback callback = (IIntResultCallback) msg.obj;
final InputConnection ic = getInputConnection();
diff --git a/core/java/com/android/internal/view/IInputContext.aidl b/core/java/com/android/internal/view/IInputContext.aidl
index 86f1293c014fb..074908acdf2aa 100644
--- a/core/java/com/android/internal/view/IInputContext.aidl
+++ b/core/java/com/android/internal/view/IInputContext.aidl
@@ -26,6 +26,7 @@ import android.view.inputmethod.InputContentInfo;
import com.android.internal.inputmethod.ICharSequenceResultCallback;
import com.android.internal.inputmethod.IExtractedTextResultCallback;
import com.android.internal.inputmethod.IIntResultCallback;
+import com.android.internal.inputmethod.ISurroundingTextResultCallback;
/**
* Interface from an input method to the application, allowing it to perform
@@ -79,4 +80,7 @@ import com.android.internal.inputmethod.IIntResultCallback;
void commitContent(in InputContentInfo inputContentInfo, int flags, in Bundle opts,
IIntResultCallback callback);
+
+ void getSurroundingText(int beforeLength, int afterLength, int flags,
+ ISurroundingTextResultCallback callback);
}
diff --git a/core/java/com/android/internal/view/InputConnectionWrapper.java b/core/java/com/android/internal/view/InputConnectionWrapper.java
index 0bf52345bc7e6..f086dd79758b7 100644
--- a/core/java/com/android/internal/view/InputConnectionWrapper.java
+++ b/core/java/com/android/internal/view/InputConnectionWrapper.java
@@ -33,6 +33,7 @@ import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputConnectionInspector;
import android.view.inputmethod.InputConnectionInspector.MissingMethodFlags;
import android.view.inputmethod.InputContentInfo;
+import android.view.inputmethod.SurroundingText;
import com.android.internal.inputmethod.CancellationGroup;
import com.android.internal.inputmethod.ResultCallbacks;
@@ -157,6 +158,38 @@ public class InputConnectionWrapper implements InputConnection {
return getResultOrNull(value, "getSelectedText()");
}
+ /**
+ * Get {@link SurroundingText} around the current cursor, with beforeLength
+ * characters of text before the cursor, afterLength characters of text after the
+ * cursor, and all of the selected text.
+ * @param beforeLength The expected length of the text before the cursor
+ * @param afterLength The expected length of the text after the cursor
+ * @param flags Supplies additional options controlling how the text is returned. May be either
+ * 0 or {@link #GET_TEXT_WITH_STYLES}.
+ * @return the surrounding text around the cursor position; the length of the returned text
+ * might be less than requested. It could also be {@code null} when the editor or system could
+ * not support this protocol.
+ */
+ @AnyThread
+ public SurroundingText getSurroundingText(int beforeLength, int afterLength, int flags) {
+ if (mCancellationGroup.isCanceled()) {
+ return null;
+ }
+ if (isMethodMissing(MissingMethodFlags.GET_SURROUNDING_TEXT)) {
+ // This method is not implemented.
+ return null;
+ }
+ final CancellationGroup.Completable.SurroundingText value =
+ mCancellationGroup.createCompletableSurroundingText();
+ try {
+ mIInputContext.getSurroundingText(beforeLength, afterLength, flags,
+ ResultCallbacks.of(value));
+ } catch (RemoteException e) {
+ return null;
+ }
+ return getResultOrNull(value, "getSurroundingText()");
+ }
+
@AnyThread
public int getCursorCapsMode(int reqModes) {
if (mCancellationGroup.isCanceled()) {
diff --git a/core/tests/coretests/src/android/view/inputmethod/SurroundingTextTest.java b/core/tests/coretests/src/android/view/inputmethod/SurroundingTextTest.java
new file mode 100644
index 0000000000000..dfbc39c764893
--- /dev/null
+++ b/core/tests/coretests/src/android/view/inputmethod/SurroundingTextTest.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2020 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 static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+import android.os.Parcel;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class SurroundingTextTest {
+
+ @Test
+ public void testSurroundingTextBasicCreation() {
+ SurroundingText surroundingText1 = new SurroundingText("test", 0, 0, 0);
+ assertThat(surroundingText1.getText(), is("test"));
+ assertThat(surroundingText1.getSelectionStart(), is(0));
+ assertThat(surroundingText1.getSelectionEnd(), is(0));
+ assertThat(surroundingText1.getOffset(), is(0));
+
+ SurroundingText surroundingText2 = new SurroundingText("", -1, -1, -1);
+ assertThat(surroundingText2.getText(), is(""));
+ assertThat(surroundingText2.getSelectionStart(), is(-1));
+ assertThat(surroundingText2.getSelectionEnd(), is(-1));
+ assertThat(surroundingText2.getOffset(), is(-1));
+
+ SurroundingText surroundingText3 = new SurroundingText("hello", 0, 5, 0);
+ assertThat(surroundingText3.getText(), is("hello"));
+ assertThat(surroundingText3.getSelectionStart(), is(0));
+ assertThat(surroundingText3.getSelectionEnd(), is(5));
+ assertThat(surroundingText3.getOffset(), is(0));
+ }
+
+ @Test
+ public void testSurroundingTextWriteToParcel() {
+ SurroundingText surroundingText = new SurroundingText("text", 0, 1, 2);
+ Parcel parcel = Parcel.obtain();
+ surroundingText.writeToParcel(parcel, 0);
+
+ parcel.setDataPosition(0);
+ SurroundingText surroundingTextFromParcel =
+ SurroundingText.CREATOR.createFromParcel(parcel);
+ assertThat(surroundingText.getText(), is("text"));
+ assertThat(surroundingText.getSelectionStart(), is(0));
+ assertThat(surroundingText.getSelectionEnd(), is(1));
+ assertThat(surroundingText.getOffset(), is(2));
+ assertThat(surroundingTextFromParcel.getText(), is("text"));
+ assertThat(surroundingTextFromParcel.getSelectionStart(), is(0));
+ assertThat(surroundingTextFromParcel.getSelectionEnd(), is(1));
+ assertThat(surroundingTextFromParcel.getOffset(), is(2));
+ }
+}
diff --git a/non-updatable-api/current.txt b/non-updatable-api/current.txt
index 24fbe00ad4b8c..b0070a3fac988 100644
--- a/non-updatable-api/current.txt
+++ b/non-updatable-api/current.txt
@@ -55449,6 +55449,7 @@ package android.view.inputmethod {
method public android.view.inputmethod.ExtractedText getExtractedText(android.view.inputmethod.ExtractedTextRequest, int);
method public android.os.Handler getHandler();
method public CharSequence getSelectedText(int);
+ method @Nullable public default android.view.inputmethod.SurroundingText getSurroundingText(@IntRange(from=0) int, @IntRange(from=0) int, int);
method public CharSequence getTextAfterCursor(int, int);
method public CharSequence getTextBeforeCursor(int, int);
method public boolean performContextMenuAction(int);
@@ -55657,6 +55658,17 @@ package android.view.inputmethod {
method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setSubtypeNameResId(int);
}
+ public final class SurroundingText implements android.os.Parcelable {
+ ctor public SurroundingText(@NonNull CharSequence, @IntRange(from=0) int, @IntRange(from=0) int, @IntRange(from=0xffffffff) int);
+ method public int describeContents();
+ method @IntRange(from=0xffffffff) public int getOffset();
+ method @IntRange(from=0) public int getSelectionEnd();
+ method @IntRange(from=0) public int getSelectionStart();
+ method @NonNull public CharSequence getText();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator