Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 02b50d49 authored by Lan Wei's avatar Lan Wei
Browse files

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
parent 4ecec9c5
Loading
Loading
Loading
Loading
+12 −0
Original line number Original line Diff line number Diff line
@@ -57319,6 +57319,7 @@ package android.view.inputmethod {
    method public android.view.inputmethod.ExtractedText getExtractedText(android.view.inputmethod.ExtractedTextRequest, int);
    method public android.view.inputmethod.ExtractedText getExtractedText(android.view.inputmethod.ExtractedTextRequest, int);
    method public android.os.Handler getHandler();
    method public android.os.Handler getHandler();
    method public CharSequence getSelectedText(int);
    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 getTextAfterCursor(int, int);
    method public CharSequence getTextBeforeCursor(int, int);
    method public CharSequence getTextBeforeCursor(int, int);
    method public boolean performContextMenuAction(int);
    method public boolean performContextMenuAction(int);
@@ -57527,6 +57528,17 @@ package android.view.inputmethod {
    method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setSubtypeNameResId(int);
    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 {
package android.view.inspector {
+44 −0
Original line number Original line Diff line number Diff line
@@ -19,6 +19,8 @@ package android.view.inputmethod;
import static android.view.OnReceiveContentCallback.Payload.SOURCE_INPUT_METHOD;
import static android.view.OnReceiveContentCallback.Payload.SOURCE_INPUT_METHOD;


import android.annotation.CallSuper;
import android.annotation.CallSuper;
import android.annotation.IntRange;
import android.annotation.Nullable;
import android.content.ClipData;
import android.content.ClipData;
import android.content.Context;
import android.content.Context;
import android.content.res.TypedArray;
import android.content.res.TypedArray;
@@ -584,6 +586,48 @@ public class BaseInputConnection implements InputConnection {
        return TextUtils.substring(content, b, b + length);
        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.
     * The default implementation turns this into the enter key.
     */
     */
+74 −7
Original line number Original line Diff line number Diff line
@@ -16,14 +16,20 @@


package android.view.inputmethod;
package android.view.inputmethod;


import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.Nullable;
import android.inputmethodservice.InputMethodService;
import android.inputmethodservice.InputMethodService;
import android.os.Bundle;
import android.os.Bundle;
import android.os.Handler;
import android.os.Handler;
import android.text.TextUtils;
import android.view.KeyCharacterMap;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import android.view.KeyEvent;


import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

/**
/**
 * The InputConnection interface is the communication channel from an
 * The InputConnection interface is the communication channel from an
 * {@link InputMethod} back to the application that is receiving its
 * {@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>
 * of each other, and the IME may use them however they see fit.</p>
 */
 */
public interface InputConnection {
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
     * Flag for use with {@link #getTextAfterCursor}, {@link #getTextBeforeCursor} and
     * {@link #getTextBeforeCursor} to have style information returned
     * {@link #getSurroundingText} to have style information returned along with the text. If not
     * along with the text. If not set, {@link #getTextAfterCursor}
     * set, {@link #getTextAfterCursor} sends only the raw text, without style or other spans. If
     * sends only the raw text, without style or other spans. If set,
     * set, it may return a complex CharSequence of both text and style spans.
     * it may return a complex CharSequence of both text and style
     * <strong>Editor authors</strong>: you should strive to send text with styles if possible, but
     * spans. <strong>Editor authors</strong>: you should strive to
     * it is not required.
     * send text with styles if possible, but it is not required.
     */
     */
    int GET_TEXT_WITH_STYLES = 0x0001;
    int GET_TEXT_WITH_STYLES = 0x0001;


@@ -263,6 +275,61 @@ public interface InputConnection {
     */
     */
    CharSequence getSelectedText(int flags);
    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
     * Retrieve the current capitalization mode in effect at the
     * current cursor position in the text. See
     * current cursor position in the text. See
+19 −0
Original line number Original line Diff line number Diff line
@@ -44,6 +44,7 @@ public final class InputConnectionInspector {
            MissingMethodFlags.GET_HANDLER,
            MissingMethodFlags.GET_HANDLER,
            MissingMethodFlags.CLOSE_CONNECTION,
            MissingMethodFlags.CLOSE_CONNECTION,
            MissingMethodFlags.COMMIT_CONTENT,
            MissingMethodFlags.COMMIT_CONTENT,
            MissingMethodFlags.GET_SURROUNDING_TEXT
    })
    })
    public @interface MissingMethodFlags {
    public @interface MissingMethodFlags {
        /**
        /**
@@ -86,6 +87,11 @@ public final class InputConnectionInspector {
         * {@link android.os.Build.VERSION_CODES#N} MR-1 and later.
         * {@link android.os.Build.VERSION_CODES#N} MR-1 and later.
         */
         */
        int COMMIT_CONTENT = 1 << 7;
        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(
    private static final Map<Class, Integer> sMissingMethodsMap = Collections.synchronizedMap(
@@ -138,6 +144,9 @@ public final class InputConnectionInspector {
        if (!hasCommitContent(clazz)) {
        if (!hasCommitContent(clazz)) {
            flags |= MissingMethodFlags.COMMIT_CONTENT;
            flags |= MissingMethodFlags.COMMIT_CONTENT;
        }
        }
        if (!hasGetSurroundingText(clazz)) {
            flags |= MissingMethodFlags.GET_SURROUNDING_TEXT;
        }
        sMissingMethodsMap.put(clazz, flags);
        sMissingMethodsMap.put(clazz, flags);
        return 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) {
    public static String getMissingMethodFlagsAsString(@MissingMethodFlags final int flags) {
        final StringBuilder sb = new StringBuilder();
        final StringBuilder sb = new StringBuilder();
        boolean isEmpty = true;
        boolean isEmpty = true;
+11 −0
Original line number Original line Diff line number Diff line
@@ -16,6 +16,7 @@


package android.view.inputmethod;
package android.view.inputmethod;


import android.annotation.Nullable;
import android.os.Bundle;
import android.os.Bundle;
import android.os.Handler;
import android.os.Handler;
import android.view.KeyEvent;
import android.view.KeyEvent;
@@ -97,6 +98,16 @@ public class InputConnectionWrapper implements InputConnection {
        return mTarget.getSelectedText(flags);
        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}
     * {@inheritDoc}
     * @throws NullPointerException if the target is {@code null}.
     * @throws NullPointerException if the target is {@code null}.
Loading