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

Commit 1c10a5c2 authored by Lan Wei's avatar Lan Wei
Browse files

Add unit tests for BaseInputConnection

Add basic unit tests for BaseInputConnection.
Also some trivial changes in BaseInputConnection:
  - Mark some files with @NonNull & @Nullable annotation.
  - Fix possible NPE in commitContent().
  - Marks public methods for overriding InputConnection interface with @Override.
  - Format file via google-java-format.py.

Bug: 240359838
Bug: 241503197
Test: atest FrameworksCoreTests:BaseInputConnectionTest
Test: atest FrameworksCoreTests:SurroundingTextTest
Test: atest CtsInputMethodTestCases:BaseInputConnectionTest

Change-Id: Idf77d69bcd4d84a48742e0d7a24398df3c0ae7f8
parent 1fd7ea10
Loading
Loading
Loading
Loading
+9 −9
Original line number Diff line number Diff line
@@ -52941,7 +52941,7 @@ package android.view.displayhash {
package android.view.inputmethod {
  public class BaseInputConnection implements android.view.inputmethod.InputConnection {
    ctor public BaseInputConnection(android.view.View, boolean);
    ctor public BaseInputConnection(@NonNull android.view.View, boolean);
    method public boolean beginBatchEdit();
    method public boolean clearMetaKeyStates(int);
    method @CallSuper public void closeConnection();
@@ -52953,24 +52953,24 @@ package android.view.inputmethod {
    method public boolean deleteSurroundingTextInCodePoints(int, int);
    method public boolean endBatchEdit();
    method public boolean finishComposingText();
    method public static int getComposingSpanEnd(android.text.Spannable);
    method public static int getComposingSpanStart(android.text.Spannable);
    method public static int getComposingSpanEnd(@NonNull android.text.Spannable);
    method public static int getComposingSpanStart(@NonNull android.text.Spannable);
    method public int getCursorCapsMode(int);
    method public android.text.Editable getEditable();
    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 android.text.Editable getEditable();
    method @Nullable public android.view.inputmethod.ExtractedText getExtractedText(android.view.inputmethod.ExtractedTextRequest, int);
    method @Nullable public android.os.Handler getHandler();
    method @Nullable public CharSequence getSelectedText(int);
    method @Nullable public CharSequence getTextAfterCursor(@IntRange(from=0) int, int);
    method @Nullable public CharSequence getTextBeforeCursor(@IntRange(from=0) int, int);
    method public boolean performContextMenuAction(int);
    method public boolean performEditorAction(int);
    method public boolean performPrivateCommand(String, android.os.Bundle);
    method public static final void removeComposingSpans(android.text.Spannable);
    method public static final void removeComposingSpans(@NonNull android.text.Spannable);
    method public boolean reportFullscreenMode(boolean);
    method public boolean requestCursorUpdates(int);
    method public boolean sendKeyEvent(android.view.KeyEvent);
    method public boolean setComposingRegion(int, int);
    method public static void setComposingSpans(android.text.Spannable);
    method public static void setComposingSpans(@NonNull android.text.Spannable);
    method public boolean setComposingText(CharSequence, int);
    method public boolean setSelection(int, int);
  }
+161 −116
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ import static android.view.ContentInfo.SOURCE_INPUT_METHOD;

import android.annotation.CallSuper;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.ClipData;
import android.content.ClipDescription;
@@ -61,8 +62,15 @@ public class BaseInputConnection implements InputConnection {
    static final Object COMPOSING = new ComposingText();

    /** @hide */
    protected final InputMethodManager mIMM;
    final View mTargetView;
    @NonNull protected final InputMethodManager mIMM;

    /**
     * Target view for the input connection.
     *
     * <p>This could be null for a fallback input connection.
     */
    @Nullable final View mTargetView;

    final boolean mFallbackMode;

    private Object[] mDefaultComposingSpans;
@@ -70,20 +78,25 @@ public class BaseInputConnection implements InputConnection {
    Editable mEditable;
    KeyCharacterMap mKeyCharacterMap;

    BaseInputConnection(InputMethodManager mgr, boolean fullEditor) {
    BaseInputConnection(@NonNull InputMethodManager mgr, boolean fullEditor) {
        mIMM = mgr;
        mTargetView = null;
        mFallbackMode = !fullEditor;
    }

    public BaseInputConnection(View targetView, boolean fullEditor) {
    public BaseInputConnection(@NonNull View targetView, boolean fullEditor) {
        mIMM = (InputMethodManager)targetView.getContext().getSystemService(
                Context.INPUT_METHOD_SERVICE);
        mTargetView = targetView;
        mFallbackMode = !fullEditor;
    }

    public static final void removeComposingSpans(Spannable text) {
    /**
     * Removes the composing spans from the given text if any.
     *
     * @param text the spannable text to remove composing spans
     */
    public static final void removeComposingSpans(@NonNull Spannable text) {
        text.removeSpan(COMPOSING);
        Object[] sps = text.getSpans(0, text.length(), Object.class);
        if (sps != null) {
@@ -96,12 +109,17 @@ public class BaseInputConnection implements InputConnection {
        }
    }

    public static void setComposingSpans(Spannable text) {
    /**
     * Removes the composing spans from the given text if any.
     *
     * @param text the spannable text to remove composing spans
     */
    public static void setComposingSpans(@NonNull Spannable text) {
        setComposingSpans(text, 0, text.length());
    }

    /** @hide */
    public static void setComposingSpans(Spannable text, int start, int end) {
    public static void setComposingSpans(@NonNull Spannable text, int start, int end) {
        final Object[] sps = text.getSpans(start, end, Object.class);
        if (sps != null) {
            for (int i=sps.length-1; i>=0; i--) {
@@ -114,7 +132,10 @@ public class BaseInputConnection implements InputConnection {
                final int fl = text.getSpanFlags(o);
                if ((fl & (Spanned.SPAN_COMPOSING | Spanned.SPAN_POINT_MARK_MASK))
                        != (Spanned.SPAN_COMPOSING | Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)) {
                    text.setSpan(o, text.getSpanStart(o), text.getSpanEnd(o),
                    text.setSpan(
                            o,
                            text.getSpanStart(o),
                            text.getSpanEnd(o),
                            (fl & ~Spanned.SPAN_POINT_MARK_MASK)
                                    | Spanned.SPAN_COMPOSING
                                    | Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
@@ -126,20 +147,24 @@ public class BaseInputConnection implements InputConnection {
                Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_COMPOSING);
    }

    public static int getComposingSpanStart(Spannable text) {
    /** Return the beginning of the range of composing text, or -1 if there's no composing text. */
    public static int getComposingSpanStart(@NonNull Spannable text) {
        return text.getSpanStart(COMPOSING);
    }

    public static int getComposingSpanEnd(Spannable text) {
    /** Return the end of the range of composing text, or -1 if there's no composing text. */
    public static int getComposingSpanEnd(@NonNull Spannable text) {
        return text.getSpanEnd(COMPOSING);
    }

    /**
     * Return the target of edit operations.  The default implementation
     * returns its own fake editable that is just used for composing text;
     * subclasses that are real text editors should override this and
     * supply their own.
     * Return the target of edit operations. The default implementation returns its own fake
     * editable that is just used for composing text; subclasses that are real text editors should
     * override this and supply their own.
     *
     * <p>Subclasses could override this method to turn null.
     */
    @Nullable
    public Editable getEditable() {
        if (mEditable == null) {
            mEditable = Editable.Factory.getInstance().newEditable("");
@@ -148,16 +173,14 @@ public class BaseInputConnection implements InputConnection {
        return mEditable;
    }

    /**
     * Default implementation does nothing.
     */
    /** Default implementation does nothing. */
    @Override
    public boolean beginBatchEdit() {
        return false;
    }

    /**
     * Default implementation does nothing.
     */
    /** Default implementation does nothing. */
    @Override
    public boolean endBatchEdit() {
        return false;
    }
@@ -165,29 +188,29 @@ public class BaseInputConnection implements InputConnection {
    /**
     * Called after only the composing region is modified (so it isn't called if the text also
     * changes).
     * <p>
     * Default implementation does nothing.
     *
     * <p>Default implementation does nothing.
     *
     * @hide
     */
    public void endComposingRegionEditInternal() {
    }
    public void endComposingRegionEditInternal() {}

    /**
     * Default implementation calls {@link #finishComposingText()} and
     * {@code setImeConsumesInput(false)}.
     * Default implementation calls {@link #finishComposingText()} and {@code
     * setImeConsumesInput(false)}.
     */
    @CallSuper
    @Override
    public void closeConnection() {
        finishComposingText();
        setImeConsumesInput(false);
    }

    /**
     * Default implementation uses
     * {@link MetaKeyKeyListener#clearMetaKeyState(long, int)
     * Default implementation uses {@link MetaKeyKeyListener#clearMetaKeyState(long, int)
     * MetaKeyKeyListener.clearMetaKeyState(long, int)} to clear the state.
     */
    @Override
    public boolean clearMetaKeyStates(int states) {
        final Editable content = getEditable();
        if (content == null) return false;
@@ -195,25 +218,24 @@ public class BaseInputConnection implements InputConnection {
        return true;
    }

    /**
     * Default implementation does nothing and returns false.
     */
    /** Default implementation does nothing and returns false. */
    @Override
    public boolean commitCompletion(CompletionInfo text) {
        return false;
    }

    /**
     * Default implementation does nothing and returns false.
     */
    /** Default implementation does nothing and returns false. */
    @Override
    public boolean commitCorrection(CorrectionInfo correctionInfo) {
        return false;
    }

    /**
     * Default implementation replaces any existing composing text with
     * the given text.  In addition, only if fallback mode, a key event is
     * sent for the new text and the current editable buffer cleared.
     * Default implementation replaces any existing composing text with the given text. In addition,
     * only if fallback mode, a key event is sent for the new text and the current editable buffer
     * cleared.
     */
    @Override
    public boolean commitText(CharSequence text, int newCursorPosition) {
        if (DEBUG) Log.v(TAG, "commitText " + text);
        replaceText(text, newCursorPosition, false);
@@ -229,18 +251,16 @@ public class BaseInputConnection implements InputConnection {
     *     If this is greater than the number of existing characters between the beginning of the
     *     text and the cursor, then this method does not fail but deletes all the characters in
     *     that range.
     * @param afterLength The number of characters after the cursor to be deleted, in code unit.
     *        If this is greater than the number of existing characters between the cursor and
     *        the end of the text, then this method does not fail but deletes all the characters in
     *        that range.
     *
     * @return {@code true} when selected text is deleted, {@code false} when either the
     *         selection is invalid or not yet attached (i.e. selection start or end is -1),
     *         or the editable text is {@code null}.
     * @param afterLength The number of characters after the cursor to be deleted, in code unit. If
     *     this is greater than the number of existing characters between the cursor and the end of
     *     the text, then this method does not fail but deletes all the characters in that range.
     * @return {@code true} when selected text is deleted, {@code false} when either the selection
     *     is invalid or not yet attached (i.e. selection start or end is -1), or the editable text
     *     is {@code null}.
     */
    @Override
    public boolean deleteSurroundingText(int beforeLength, int afterLength) {
        if (DEBUG) Log.v(TAG, "deleteSurroundingText " + beforeLength
                + " / " + afterLength);
        if (DEBUG) Log.v(TAG, "deleteSurroundingText " + beforeLength + " / " + afterLength);
        final Editable content = getEditable();
        if (content == null) return false;

@@ -399,18 +419,18 @@ public class BaseInputConnection implements InputConnection {
    /**
     * The default implementation performs the deletion around the current selection position of the
     * editable text.
     *
     * @param beforeLength The number of characters before the cursor to be deleted, in code points.
     *     If this is greater than the number of existing characters between the beginning of the
     *     text and the cursor, then this method does not fail but deletes all the characters in
     *     that range.
     * @param afterLength The number of characters after the cursor to be deleted, in code points.
     *        If this is greater than the number of existing characters between the cursor and
     *        the end of the text, then this method does not fail but deletes all the characters in
     *        that range.
     *     If this is greater than the number of existing characters between the cursor and the end
     *     of the text, then this method does not fail but deletes all the characters in that range.
     */
    @Override
    public boolean deleteSurroundingTextInCodePoints(int beforeLength, int afterLength) {
        if (DEBUG) Log.v(TAG, "deleteSurroundingText " + beforeLength
                + " / " + afterLength);
        if (DEBUG) Log.v(TAG, "deleteSurroundingText " + beforeLength + " / " + afterLength);
        final Editable content = getEditable();
        if (content == null) return false;

@@ -466,10 +486,11 @@ public class BaseInputConnection implements InputConnection {
    }

    /**
     * The default implementation removes the composing state from the
     * current editable text.  In addition, only if fallback mode, a key event is
     * sent for the new text and the current editable buffer cleared.
     * The default implementation removes the composing state from the current editable text. In
     * addition, only if fallback mode, a key event is sent for the new text and the current
     * editable buffer cleared.
     */
    @Override
    public boolean finishComposingText() {
        if (DEBUG) Log.v(TAG, "finishComposingText");
        final Editable content = getEditable();
@@ -485,10 +506,11 @@ public class BaseInputConnection implements InputConnection {
    }

    /**
     * The default implementation uses TextUtils.getCapsMode to get the
     * cursor caps mode for the current selection position in the editable
     * text, unless in fallback mode in which case 0 is always returned.
     * The default implementation uses TextUtils.getCapsMode to get the cursor caps mode for the
     * current selection position in the editable text, unless in fallback mode in which case 0 is
     * always returned.
     */
    @Override
    public int getCursorCapsMode(int reqModes) {
        if (mFallbackMode) return 0;

@@ -507,17 +529,18 @@ public class BaseInputConnection implements InputConnection {
        return TextUtils.getCapsMode(content, a, reqModes);
    }

    /**
     * The default implementation always returns null.
     */
    /** The default implementation always returns null. */
    @Override
    @Nullable
    public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
        return null;
    }

    /**
     * The default implementation returns the given amount of text from the
     * current cursor position in the buffer.
     * The default implementation returns the given amount of text from the current cursor position
     * in the buffer.
     */
    @Override
    @Nullable
    public CharSequence getTextBeforeCursor(@IntRange(from = 0) int length, int flags) {
        Preconditions.checkArgumentNonnegative(length);
@@ -549,9 +572,10 @@ public class BaseInputConnection implements InputConnection {
    }

    /**
     * The default implementation returns the text currently selected, or null if none is
     * selected.
     * The default implementation returns the text currently selected, or null if none is selected.
     */
    @Override
    @Nullable
    public CharSequence getSelectedText(int flags) {
        final Editable content = getEditable();
        if (content == null) return null;
@@ -574,9 +598,10 @@ public class BaseInputConnection implements InputConnection {
    }

    /**
     * The default implementation returns the given amount of text from the
     * current cursor position in the buffer.
     * The default implementation returns the given amount of text from the current cursor position
     * in the buffer.
     */
    @Override
    @Nullable
    public CharSequence getTextAfterCursor(@IntRange(from = 0) int length, int flags) {
        Preconditions.checkArgumentNonnegative(length);
@@ -602,7 +627,6 @@ public class BaseInputConnection implements InputConnection {
            length = content.length() - b;
        }


        if ((flags&GET_TEXT_WITH_STYLES) != 0) {
            return content.subSequence(b, b + length);
        }
@@ -613,6 +637,7 @@ public class BaseInputConnection implements InputConnection {
     * The default implementation returns the given amount of text around the current cursor
     * position in the buffer.
     */
    @Override
    @Nullable
    public SurroundingText getSurroundingText(
            @IntRange(from = 0) int beforeLength, @IntRange(from = 0) int afterLength, int flags) {
@@ -659,60 +684,75 @@ public class BaseInputConnection implements InputConnection {
                surroundingText, selStart - startPos, selEnd - startPos, startPos);
    }

    /**
     * The default implementation turns this into the enter key.
     */
    /** The default implementation turns this into the enter key. */
    @Override
    public boolean performEditorAction(int actionCode) {
        long eventTime = SystemClock.uptimeMillis();
        sendKeyEvent(new KeyEvent(eventTime, eventTime,
                KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER, 0, 0,
                KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
                KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
        sendKeyEvent(
                new KeyEvent(
                        eventTime,
                        eventTime,
                        KeyEvent.ACTION_DOWN,
                        KeyEvent.KEYCODE_ENTER,
                        0,
                        0,
                        KeyCharacterMap.VIRTUAL_KEYBOARD,
                        0,
                        KeyEvent.FLAG_SOFT_KEYBOARD
                                | KeyEvent.FLAG_KEEP_TOUCH_MODE
                                | KeyEvent.FLAG_EDITOR_ACTION));
        sendKeyEvent(new KeyEvent(SystemClock.uptimeMillis(), eventTime,
                KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER, 0, 0,
                KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
                KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
        sendKeyEvent(
                new KeyEvent(
                        SystemClock.uptimeMillis(),
                        eventTime,
                        KeyEvent.ACTION_UP,
                        KeyEvent.KEYCODE_ENTER,
                        0,
                        0,
                        KeyCharacterMap.VIRTUAL_KEYBOARD,
                        0,
                        KeyEvent.FLAG_SOFT_KEYBOARD
                                | KeyEvent.FLAG_KEEP_TOUCH_MODE
                                | KeyEvent.FLAG_EDITOR_ACTION));
        return true;
    }

    /**
     * The default implementation does nothing.
     */
    /** The default implementation does nothing. */
    @Override
    public boolean performContextMenuAction(int id) {
        return false;
    }

    /**
     * The default implementation does nothing.
     */
    /** The default implementation does nothing. */
    @Override
    public boolean performPrivateCommand(String action, Bundle data) {
        return false;
    }

    /**
     * The default implementation does nothing.
     */
    /** The default implementation does nothing. */
    @Override
    public boolean requestCursorUpdates(int cursorUpdateMode) {
        return false;
    }

    @Override
    @Nullable
    public Handler getHandler() {
        return null;
    }

    /**
     * The default implementation places the given text into the editable,
     * replacing any existing composing text.  The new text is marked as
     * in a composing state with the composing style.
     * The default implementation places the given text into the editable, replacing any existing
     * composing text. The new text is marked as in a composing state with the composing style.
     */
    @Override
    public boolean setComposingText(CharSequence text, int newCursorPosition) {
        if (DEBUG) Log.v(TAG, "setComposingText " + text);
        replaceText(text, newCursorPosition, true);
        return true;
    }

    @Override
    public boolean setComposingRegion(int start, int end) {
        final Editable content = getEditable();
        if (content != null) {
@@ -735,7 +775,10 @@ public class BaseInputConnection implements InputConnection {
            ensureDefaultComposingSpans();
            if (mDefaultComposingSpans != null) {
                for (int i = 0; i < mDefaultComposingSpans.length; ++i) {
                    content.setSpan(mDefaultComposingSpans[i], a, b,
                    content.setSpan(
                            mDefaultComposingSpans[i],
                            a,
                            b,
                            Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_COMPOSING);
                }
            }
@@ -751,10 +794,8 @@ public class BaseInputConnection implements InputConnection {
        return true;
    }

    /**
     * The default implementation changes the selection position in the
     * current editable text.
     */
    /** The default implementation changes the selection position in the current editable text. */
    @Override
    public boolean setSelection(int start, int end) {
        if (DEBUG) Log.v(TAG, "setSelection " + start + ", " + end);
        final Editable content = getEditable();
@@ -779,17 +820,17 @@ public class BaseInputConnection implements InputConnection {
    }

    /**
     * Provides standard implementation for sending a key event to the window
     * attached to the input connection's view.
     * Provides standard implementation for sending a key event to the window attached to the input
     * connection's view.
     */
    @Override
    public boolean sendKeyEvent(KeyEvent event) {
        mIMM.dispatchKeyEventFromInputMethod(mTargetView, event);
        return false;
    }

    /**
     * Updates InputMethodManager with the current fullscreen mode.
     */
    /** Updates InputMethodManager with the current fullscreen mode. */
    @Override
    public boolean reportFullscreenMode(boolean enabled) {
        return true;
    }
@@ -934,8 +975,7 @@ public class BaseInputConnection implements InputConnection {
            newCursorPosition += a;
        }
        if (newCursorPosition < 0) newCursorPosition = 0;
        if (newCursorPosition > content.length())
            newCursorPosition = content.length();
        if (newCursorPosition > content.length()) newCursorPosition = content.length();
        Selection.setSelection(content, newCursorPosition);

        content.replace(a, b, text);
@@ -950,11 +990,16 @@ public class BaseInputConnection implements InputConnection {
    }

    /**
     * Default implementation which invokes {@link View#performReceiveContent} on the target
     * view if the view {@link View#getReceiveContentMimeTypes allows} content insertion;
     * otherwise returns false without any side effects.
     * Default implementation which invokes {@link View#performReceiveContent} on the target view if
     * the view {@link View#getReceiveContentMimeTypes allows} content insertion; otherwise returns
     * false without any side effects.
     */
    @Override
    public boolean commitContent(InputContentInfo inputContentInfo, int flags, Bundle opts) {
        if (mTargetView == null) {
            return false;
        }

        ClipDescription description = inputContentInfo.getDescription();
        if (mTargetView.getReceiveContentMimeTypes() == null) {
            if (DEBUG) {