IME API: InputConnection#getSurroudingText(int, int, int)
Introduce a new class SurroundingText and a new API in InputConnection to support retrieving surrounding text as an atomic request. SurroundingText is the class for wrapping the text and sggestion info. InputConnection#getSurroudingText() will return an SurroudingText object if the protocol is supported. Test: atest FrameworksCoreTests:SurroundingTextTest Test: atest CtsInputMethodTestCases:BaseInputConnectionTest BUG: 167947745 Change-Id: I2eb9ef5ba61a0e033007089da80f81548108621e
This commit is contained in:
@@ -57319,6 +57319,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);
|
||||
@@ -57527,6 +57528,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<android.view.inputmethod.SurroundingText> CREATOR;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
package android.view.inspector {
|
||||
|
||||
@@ -19,6 +19,8 @@ package android.view.inputmethod;
|
||||
import static android.view.OnReceiveContentCallback.Payload.SOURCE_INPUT_METHOD;
|
||||
|
||||
import android.annotation.CallSuper;
|
||||
import android.annotation.IntRange;
|
||||
import android.annotation.Nullable;
|
||||
import android.content.ClipData;
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
@@ -584,6 +586,48 @@ public class BaseInputConnection implements InputConnection {
|
||||
return TextUtils.substring(content, b, b + length);
|
||||
}
|
||||
|
||||
/**
|
||||
* The default implementation returns the given amount of text around the current cursor
|
||||
* position in the buffer.
|
||||
*/
|
||||
@Nullable
|
||||
public SurroundingText getSurroundingText(
|
||||
@IntRange(from = 0) int beforeLength, @IntRange(from = 0) int afterLength, int flags) {
|
||||
final Editable content = getEditable();
|
||||
if (content == null) return null;
|
||||
|
||||
int selStart = Selection.getSelectionStart(content);
|
||||
int selEnd = Selection.getSelectionEnd(content);
|
||||
|
||||
// Guard against the case where the cursor has not been positioned yet.
|
||||
if (selStart < 0 || selEnd < 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (selStart > selEnd) {
|
||||
int tmp = selStart;
|
||||
selStart = selEnd;
|
||||
selEnd = tmp;
|
||||
}
|
||||
|
||||
int contentLength = content.length();
|
||||
int startPos = selStart - beforeLength;
|
||||
int endPos = selEnd + afterLength;
|
||||
|
||||
// Guards the start and end pos within range [0, contentLength].
|
||||
startPos = Math.max(0, startPos);
|
||||
endPos = Math.min(contentLength, endPos);
|
||||
|
||||
CharSequence surroundingText;
|
||||
if ((flags & GET_TEXT_WITH_STYLES) != 0) {
|
||||
surroundingText = content.subSequence(startPos, endPos);
|
||||
} else {
|
||||
surroundingText = TextUtils.substring(content, startPos, endPos);
|
||||
}
|
||||
return new SurroundingText(
|
||||
surroundingText, selStart - startPos, selEnd - startPos, startPos);
|
||||
}
|
||||
|
||||
/**
|
||||
* The default implementation turns this into the enter key.
|
||||
*/
|
||||
|
||||
@@ -16,14 +16,20 @@
|
||||
|
||||
package android.view.inputmethod;
|
||||
|
||||
import android.annotation.IntDef;
|
||||
import android.annotation.IntRange;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.inputmethodservice.InputMethodService;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.text.TextUtils;
|
||||
import android.view.KeyCharacterMap;
|
||||
import android.view.KeyEvent;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
/**
|
||||
* The InputConnection interface is the communication channel from an
|
||||
* {@link InputMethod} back to the application that is receiving its
|
||||
@@ -122,14 +128,20 @@ import android.view.KeyEvent;
|
||||
* of each other, and the IME may use them however they see fit.</p>
|
||||
*/
|
||||
public interface InputConnection {
|
||||
/** @hide */
|
||||
@IntDef(flag = true, prefix = { "GET_TEXT_" }, value = {
|
||||
GET_TEXT_WITH_STYLES,
|
||||
})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@interface GetTextType {}
|
||||
|
||||
/**
|
||||
* Flag for use with {@link #getTextAfterCursor} and
|
||||
* {@link #getTextBeforeCursor} to have style information returned
|
||||
* along with the text. If not set, {@link #getTextAfterCursor}
|
||||
* sends only the raw text, without style or other spans. If set,
|
||||
* it may return a complex CharSequence of both text and style
|
||||
* spans. <strong>Editor authors</strong>: you should strive to
|
||||
* send text with styles if possible, but it is not required.
|
||||
* Flag for use with {@link #getTextAfterCursor}, {@link #getTextBeforeCursor} and
|
||||
* {@link #getSurroundingText} to have style information returned along with the text. If not
|
||||
* set, {@link #getTextAfterCursor} sends only the raw text, without style or other spans. If
|
||||
* set, it may return a complex CharSequence of both text and style spans.
|
||||
* <strong>Editor authors</strong>: you should strive to send text with styles if possible, but
|
||||
* it is not required.
|
||||
*/
|
||||
int GET_TEXT_WITH_STYLES = 0x0001;
|
||||
|
||||
@@ -263,6 +275,61 @@ public interface InputConnection {
|
||||
*/
|
||||
CharSequence getSelectedText(int flags);
|
||||
|
||||
/**
|
||||
* Gets the surrounding text around the current cursor, with <var>beforeLength</var> characters
|
||||
* of text before the cursor (start of the selection), <var>afterLength</var> characters of text
|
||||
* after the cursor (end of the selection), and all of the selected text.
|
||||
*
|
||||
* <p>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.
|
||||
*
|
||||
* <p>This method does not affect the text in the editor in any way, nor does it affect the
|
||||
* selection or composing spans.</p>
|
||||
*
|
||||
* <p>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.</p>
|
||||
*
|
||||
* <p><strong>IME authors:</strong> 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.</p>
|
||||
*
|
||||
* @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
|
||||
* <var>beforeLength</var> and <var>afterLength</var> .
|
||||
*/
|
||||
@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
|
||||
|
||||
@@ -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 Map<Class, Integer> sMissingMethodsMap = Collections.synchronizedMap(
|
||||
@@ -138,6 +144,9 @@ public final class InputConnectionInspector {
|
||||
if (!hasCommitContent(clazz)) {
|
||||
flags |= MissingMethodFlags.COMMIT_CONTENT;
|
||||
}
|
||||
if (!hasGetSurroundingText(clazz)) {
|
||||
flags |= MissingMethodFlags.GET_SURROUNDING_TEXT;
|
||||
}
|
||||
sMissingMethodsMap.put(clazz, flags);
|
||||
return flags;
|
||||
}
|
||||
@@ -216,6 +225,16 @@ public final class InputConnectionInspector {
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean hasGetSurroundingText(@NonNull final Class clazz) {
|
||||
try {
|
||||
final Method method = clazz.getMethod("getSurroundingText", int.class, int.class,
|
||||
int.class);
|
||||
return !Modifier.isAbstract(method.getModifiers());
|
||||
} catch (NoSuchMethodException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static String getMissingMethodFlagsAsString(@MissingMethodFlags final int flags) {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
boolean isEmpty = true;
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package android.view.inputmethod;
|
||||
|
||||
import android.annotation.Nullable;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.view.KeyEvent;
|
||||
@@ -97,6 +98,16 @@ public class InputConnectionWrapper implements InputConnection {
|
||||
return mTarget.getSelectedText(flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* @throws NullPointerException if the target is {@code null}.
|
||||
*/
|
||||
@Nullable
|
||||
@Override
|
||||
public SurroundingText getSurroundingText(int beforeLength, int afterLength, int flags) {
|
||||
return mTarget.getSurroundingText(beforeLength, afterLength, flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* @throws NullPointerException if the target is {@code null}.
|
||||
|
||||
19
core/java/android/view/inputmethod/SurroundingText.aidl
Normal file
19
core/java/android/view/inputmethod/SurroundingText.aidl
Normal file
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
parcelable SurroundingText;
|
||||
150
core/java/android/view/inputmethod/SurroundingText.java
Normal file
150
core/java/android/view/inputmethod/SurroundingText.java
Normal file
@@ -0,0 +1,150 @@
|
||||
/*
|
||||
* 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 android.annotation.IntRange;
|
||||
import android.annotation.NonNull;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.text.TextUtils;
|
||||
|
||||
/**
|
||||
* Information about the surrounding text around the cursor for use by an input method.
|
||||
*
|
||||
* <p>This contains information about the text and the selection relative to the text. </p>
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* <p>This needs to be the position relative to the {@link #mText} instead of the real position
|
||||
* in the editor.</p>
|
||||
*/
|
||||
@IntRange(from = 0)
|
||||
private final int mSelectionStart;
|
||||
|
||||
/**
|
||||
* The text offset of the end of the selection in the surrounding text.
|
||||
*
|
||||
* <p>This needs to be the position relative to the {@link #mText} instead of the real position
|
||||
* in the editor.</p>
|
||||
*/
|
||||
@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.
|
||||
*
|
||||
* <p>-1 indicates the offset information is unknown.</p>
|
||||
*/
|
||||
@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.
|
||||
*
|
||||
* <p>-1 indicates the offset information is unknown.</p>
|
||||
*/
|
||||
@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.Creator<SurroundingText> CREATOR =
|
||||
new Parcelable.Creator<SurroundingText>() {
|
||||
@NonNull
|
||||
public SurroundingText createFromParcel(Parcel in) {
|
||||
final CharSequence text =
|
||||
TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
|
||||
final int selectionHead = in.readInt();
|
||||
final int selectionEnd = in.readInt();
|
||||
final int offset = in.readInt();
|
||||
return new SurroundingText(
|
||||
text == null ? "" : text, selectionHead, selectionEnd, offset);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public SurroundingText[] newArray(int size) {
|
||||
return new SurroundingText[size];
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -82,6 +82,7 @@ import android.view.inputmethod.ExtractedTextRequest;
|
||||
import android.view.inputmethod.InputConnection;
|
||||
import android.view.inputmethod.InputContentInfo;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.view.inputmethod.SurroundingText;
|
||||
import android.view.inspector.InspectableProperty;
|
||||
import android.view.inspector.InspectableProperty.EnumEntry;
|
||||
import android.widget.RemoteViews.OnClickHandler;
|
||||
@@ -5996,6 +5997,12 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
|
||||
return mTarget.getSelectedText(flags);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SurroundingText getSurroundingText(int beforeLength, int afterLength, int flags) {
|
||||
if (mTarget == null) return null;
|
||||
return mTarget.getSurroundingText(beforeLength, afterLength, flags);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCursorCapsMode(int reqModes) {
|
||||
if (mTarget == null) return InputType.TYPE_TEXT_FLAG_CAP_SENTENCES;
|
||||
|
||||
@@ -263,6 +263,16 @@ public final class CancellationGroup {
|
||||
super(factory);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Completable object of {@link android.view.inputmethod.SurroundingText}.
|
||||
*/
|
||||
public static final class SurroundingText
|
||||
extends Values<android.view.inputmethod.SurroundingText> {
|
||||
private SurroundingText(@NonNull CancellationGroup factory) {
|
||||
super(factory);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -292,6 +302,16 @@ public final class CancellationGroup {
|
||||
return new Completable.ExtractedText(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return an instance of {@link Completable.SurroundingText} that is associated with this
|
||||
* {@link CancellationGroup}.
|
||||
*/
|
||||
@AnyThread
|
||||
public Completable.SurroundingText createCompletableSurroundingText() {
|
||||
return new Completable.SurroundingText(this);
|
||||
}
|
||||
|
||||
|
||||
@AnyThread
|
||||
private boolean registerLatch(@NonNull CountDownLatch latch) {
|
||||
synchronized (mLock) {
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* 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 com.android.internal.inputmethod;
|
||||
|
||||
import android.view.inputmethod.SurroundingText;
|
||||
|
||||
oneway interface ISurroundingTextResultCallback {
|
||||
void onResult(in SurroundingText result);
|
||||
}
|
||||
@@ -129,4 +129,32 @@ public final class ResultCallbacks {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates {@link ISurroundingTextResultCallback.Stub} that is to set
|
||||
* {@link CancellationGroup.Completable.SurroundingText} when receiving the result.
|
||||
*
|
||||
* @param value {@link CancellationGroup.Completable.SurroundingText} to be set when receiving
|
||||
* the result.
|
||||
* @return {@link ISurroundingTextResultCallback.Stub} that can be passed as a binder IPC
|
||||
* parameter.
|
||||
*/
|
||||
@AnyThread
|
||||
public static ISurroundingTextResultCallback.Stub of(
|
||||
@NonNull CancellationGroup.Completable.SurroundingText value) {
|
||||
final AtomicReference<WeakReference<CancellationGroup.Completable.SurroundingText>>
|
||||
atomicRef = new AtomicReference<>(new WeakReference<>(value));
|
||||
|
||||
return new ISurroundingTextResultCallback.Stub() {
|
||||
@BinderThread
|
||||
@Override
|
||||
public void onResult(android.view.inputmethod.SurroundingText result) {
|
||||
final CancellationGroup.Completable.SurroundingText value = unwrap(atomicRef);
|
||||
if (value == null) {
|
||||
return;
|
||||
}
|
||||
value.onComplete(result);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,11 +35,13 @@ 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.annotations.GuardedBy;
|
||||
import com.android.internal.inputmethod.ICharSequenceResultCallback;
|
||||
import com.android.internal.inputmethod.IExtractedTextResultCallback;
|
||||
import com.android.internal.inputmethod.IIntResultCallback;
|
||||
import com.android.internal.inputmethod.ISurroundingTextResultCallback;
|
||||
import com.android.internal.os.SomeArgs;
|
||||
|
||||
public abstract class IInputConnectionWrapper extends IInputContext.Stub {
|
||||
@@ -70,6 +72,8 @@ public abstract class IInputConnectionWrapper extends IInputContext.Stub {
|
||||
private static final int DO_REQUEST_UPDATE_CURSOR_ANCHOR_INFO = 140;
|
||||
private static final int DO_CLOSE_CONNECTION = 150;
|
||||
private static final int DO_COMMIT_CONTENT = 160;
|
||||
private static final int DO_GET_SURROUNDING_TEXT = 41;
|
||||
|
||||
|
||||
@GuardedBy("mLock")
|
||||
@Nullable
|
||||
@@ -127,6 +131,21 @@ public abstract class IInputConnectionWrapper extends IInputContext.Stub {
|
||||
dispatchMessage(mH.obtainMessage(DO_GET_SELECTED_TEXT, flags, 0 /* unused */, callback));
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatches the request for retrieving surrounding text.
|
||||
*
|
||||
* <p>See {@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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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 <var>beforeLength</var>
|
||||
* characters of text before the cursor, <var>afterLength</var> 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()) {
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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<android.view.inputmethod.SurroundingText> CREATOR;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
package android.view.inspector {
|
||||
|
||||
Reference in New Issue
Block a user