blob: 8833f41a810d8bfefb826825ea23098881b8bae8 [file] [log] [blame]
/*
* Copyright (C) 2008 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.cts;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.testng.Assert.expectThrows;
import android.content.ClipDescription;
import android.net.Uri;
import android.os.Bundle;
import android.text.Editable;
import android.text.Selection;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.Spanned;
import android.text.TextUtils;
import android.view.View;
import android.view.inputmethod.BaseInputConnection;
import android.view.inputmethod.CompletionInfo;
import android.view.inputmethod.ExtractedTextRequest;
import android.view.inputmethod.InputContentInfo;
import android.view.inputmethod.InputMethodManager;
import android.view.inputmethod.SurroundingText;
import android.view.inputmethod.TextSnapshot;
import android.view.inputmethod.cts.util.InputConnectionTestUtils;
import androidx.annotation.NonNull;
import androidx.test.filters.MediumTest;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
@MediumTest
@RunWith(AndroidJUnit4.class)
public class BaseInputConnectionTest {
private static final int CAPS_MODE_MASK = TextUtils.CAP_MODE_CHARACTERS
| TextUtils.CAP_MODE_WORDS | TextUtils.CAP_MODE_SENTENCES;
private static BaseInputConnection createBaseInputConnection() {
final View view = new View(InstrumentationRegistry.getInstrumentation().getTargetContext());
return new BaseInputConnection(view, true);
}
@Test
public void testDefaultMethods() {
// These methods are default to return fixed result.
final BaseInputConnection connection = createBaseInputConnection();
assertFalse(connection.beginBatchEdit());
assertFalse(connection.endBatchEdit());
// only fit for test default implementation of commitCompletion.
int completionId = 1;
String completionString = "commitCompletion test";
assertFalse(connection.commitCompletion(new CompletionInfo(completionId,
0, completionString)));
assertNull(connection.getExtractedText(new ExtractedTextRequest(), 0));
// only fit for test default implementation of performEditorAction.
int actionCode = 1;
int actionId = 2;
String action = "android.intent.action.MAIN";
assertTrue(connection.performEditorAction(actionCode));
assertFalse(connection.performContextMenuAction(actionId));
assertFalse(connection.performPrivateCommand(action, new Bundle()));
}
@Test
public void testOpComposingSpans() {
Spannable text = new SpannableString("Test ComposingSpans");
BaseInputConnection.setComposingSpans(text);
assertTrue(BaseInputConnection.getComposingSpanStart(text) > -1);
assertTrue(BaseInputConnection.getComposingSpanEnd(text) > -1);
BaseInputConnection.removeComposingSpans(text);
assertTrue(BaseInputConnection.getComposingSpanStart(text) == -1);
assertTrue(BaseInputConnection.getComposingSpanEnd(text) == -1);
}
/**
* getEditable: Return the target of edit operations. The default implementation
* returns its own fake editable that is just used for composing text.
* clearMetaKeyStates: Default implementation uses
* MetaKeyKeyListener#clearMetaKeyState(long, int) to clear the state.
* BugId:1738511
* commitText: Default implementation replaces any existing composing text with the given
* text.
* deleteSurroundingText: The default implementation performs the deletion around the current
* selection position of the editable text.
* getCursorCapsMode: The default implementation uses TextUtils.getCapsMode to get the
* cursor caps mode for the current selection position in the editable text.
* TextUtils.getCapsMode is tested fully in TextUtilsTest#testGetCapsMode.
* getTextBeforeCursor, getTextAfterCursor: The default implementation performs the deletion
* around the current selection position of the editable text.
* setSelection: changes the selection position in the current editable text.
*/
@Test
public void testOpTextMethods() {
final BaseInputConnection connection = createBaseInputConnection();
// return is an default Editable instance with empty source
final Editable text = connection.getEditable();
assertNotNull(text);
assertEquals(0, text.length());
// Test commitText, not default fake mode
CharSequence str = "TestCommit ";
Editable inputText = Editable.Factory.getInstance().newEditable(str);
connection.commitText(inputText, inputText.length());
final Editable text2 = connection.getEditable();
int strLength = str.length();
assertEquals(strLength, text2.length());
assertEquals(str.toString(), text2.toString());
assertEquals(TextUtils.CAP_MODE_WORDS,
connection.getCursorCapsMode(TextUtils.CAP_MODE_WORDS));
int offLength = 3;
CharSequence expected = str.subSequence(strLength - offLength, strLength);
assertEquals(expected.toString(), connection.getTextBeforeCursor(offLength,
BaseInputConnection.GET_TEXT_WITH_STYLES).toString());
connection.setSelection(0, 0);
expected = str.subSequence(0, offLength);
assertEquals(expected.toString(), connection.getTextAfterCursor(offLength,
BaseInputConnection.GET_TEXT_WITH_STYLES).toString());
// Test deleteSurroundingText
int end = text2.length();
connection.setSelection(end, end);
// Delete the ending space
assertTrue(connection.deleteSurroundingText(1, 2));
Editable text3 = connection.getEditable();
assertEquals(strLength - 1, text3.length());
String expectedDelString = "TestCommit";
assertEquals(expectedDelString, text3.toString());
}
/**
* finishComposingText: The default implementation removes the composing state from the
* current editable text.
* setComposingText: The default implementation places the given text into the editable,
* replacing any existing composing text
*/
@Test
public void testFinishComposingText() {
final BaseInputConnection connection = createBaseInputConnection();
CharSequence str = "TestFinish";
Editable inputText = Editable.Factory.getInstance().newEditable(str);
connection.commitText(inputText, inputText.length());
final Editable text = connection.getEditable();
// Test finishComposingText, not default fake mode
BaseInputConnection.setComposingSpans(text);
assertTrue(BaseInputConnection.getComposingSpanStart(text) > -1);
assertTrue(BaseInputConnection.getComposingSpanEnd(text) > -1);
connection.finishComposingText();
assertTrue(BaseInputConnection.getComposingSpanStart(text) == -1);
assertTrue(BaseInputConnection.getComposingSpanEnd(text) == -1);
}
/**
* Updates InputMethodManager with the current fullscreen mode.
*/
@Test
public void testReportFullscreenMode() {
final InputMethodManager imm = InstrumentationRegistry.getInstrumentation()
.getTargetContext().getSystemService(InputMethodManager.class);
final BaseInputConnection connection = createBaseInputConnection();
connection.reportFullscreenMode(false);
assertFalse(imm.isFullscreenMode());
connection.reportFullscreenMode(true);
// Only IMEs are allowed to report full-screen mode. Calling this method from the
// application should have no effect.
assertFalse(imm.isFullscreenMode());
}
/**
* An utility method to create an instance of {@link BaseInputConnection} from {@link Editable}.
*
* @param editable the initial text.
* @return {@link BaseInputConnection} instantiated in the full editor mode with
* {@code editable}.
*/
private static BaseInputConnection createConnection(@NonNull Editable editable) {
final View view = new View(InstrumentationRegistry.getInstrumentation().getTargetContext());
return new BaseInputConnection(view, true) {
@Override
public Editable getEditable() {
return editable;
}
};
}
/**
* An utility method to create an instance of {@link BaseInputConnection} in the full editor
* mode with an initial text and selection range.
*
* @param source the initial text.
* @return {@link BaseInputConnection} instantiated in the full editor mode with {@code source}
* and selection range from {@code selectionStart} to {@code selectionEnd}
*/
private static BaseInputConnection createConnectionWithSelection(CharSequence source) {
final int selectionStart = Selection.getSelectionStart(source);
final int selectionEnd = Selection.getSelectionEnd(source);
final Editable editable = Editable.Factory.getInstance().newEditable(source);
Selection.setSelection(editable, selectionStart, selectionEnd);
return createConnection(editable);
}
private static void verifyDeleteSurroundingTextMain(final String initialState,
final int deleteBefore, final int deleteAfter, final String expectedState) {
verifyDeleteSurroundingTextMain(initialState, deleteBefore, deleteAfter, expectedState,
false /* clearSelection */);
}
private static void verifyDeleteSurroundingTextMain(final String initialState,
final int deleteBefore, final int deleteAfter, final String expectedState,
final boolean clearSelection) {
final CharSequence source = clearSelection ? initialState
: InputConnectionTestUtils.formatString(initialState);
final BaseInputConnection ic = createConnectionWithSelection(source);
if (clearSelection) {
Selection.removeSelection(ic.getEditable());
}
final boolean result = ic.deleteSurroundingText(deleteBefore, deleteAfter);
final CharSequence expectedString = clearSelection ? expectedState
: InputConnectionTestUtils.formatString(expectedState);
final int expectedSelectionStart = Selection.getSelectionStart(expectedString);
final int expectedSelectionEnd = Selection.getSelectionEnd(expectedString);
// It is sufficient to check the surrounding text up to source.length() characters, because
// InputConnection.deleteSurroundingText() is not supposed to increase the text length.
final int retrievalLength = source.length();
if (!result) {
assertEquals(expectedState, ic.getEditable().toString());
return;
} else if (clearSelection) {
fail("deleteSurroundingText should return false for invalid selection");
}
if (expectedSelectionStart == 0) {
assertTrue(TextUtils.isEmpty(ic.getTextBeforeCursor(retrievalLength, 0)));
} else {
assertEquals(expectedString.subSequence(0, expectedSelectionStart).toString(),
ic.getTextBeforeCursor(retrievalLength, 0).toString());
}
if (expectedSelectionStart == expectedSelectionEnd) {
assertTrue(TextUtils.isEmpty(ic.getSelectedText(0))); // null is allowed.
} else {
assertEquals(expectedString.subSequence(expectedSelectionStart,
expectedSelectionEnd).toString(), ic.getSelectedText(0).toString());
}
if (expectedSelectionEnd == expectedString.length()) {
assertTrue(TextUtils.isEmpty(ic.getTextAfterCursor(retrievalLength, 0)));
} else {
assertEquals(expectedString.subSequence(expectedSelectionEnd,
expectedString.length()).toString(),
ic.getTextAfterCursor(retrievalLength, 0).toString());
}
}
/**
* Tests {@link BaseInputConnection#deleteSurroundingText(int, int)} comprehensively.
*/
@Test
public void testDeleteSurroundingText() {
verifyDeleteSurroundingTextMain("0123456789", 1, 2, "0123456789",
true /* clearSelection*/);
verifyDeleteSurroundingTextMain("012[]3456789", 0, 0, "012[]3456789");
verifyDeleteSurroundingTextMain("012[]3456789", -1, -1, "012[]3456789");
verifyDeleteSurroundingTextMain("012[]3456789", 1, 2, "01[]56789");
verifyDeleteSurroundingTextMain("012[]3456789", 10, 1, "[]456789");
verifyDeleteSurroundingTextMain("012[]3456789", 1, 10, "01[]");
verifyDeleteSurroundingTextMain("[]0123456789", 3, 3, "[]3456789");
verifyDeleteSurroundingTextMain("0123456789[]", 3, 3, "0123456[]");
verifyDeleteSurroundingTextMain("012[345]6789", 0, 0, "012[345]6789");
verifyDeleteSurroundingTextMain("012[345]6789", -1, -1, "012[345]6789");
verifyDeleteSurroundingTextMain("012[345]6789", 1, 2, "01[345]89");
verifyDeleteSurroundingTextMain("012[345]6789", 10, 1, "[345]789");
verifyDeleteSurroundingTextMain("012[345]6789", 1, 10, "01[345]");
verifyDeleteSurroundingTextMain("[012]3456789", 3, 3, "[012]6789");
verifyDeleteSurroundingTextMain("0123456[789]", 3, 3, "0123[789]");
verifyDeleteSurroundingTextMain("[0123456789]", 0, 0, "[0123456789]");
verifyDeleteSurroundingTextMain("[0123456789]", 1, 1, "[0123456789]");
// Surrogate characters do not have any special meanings. Validating the character sequence
// is beyond the goal of this API.
verifyDeleteSurroundingTextMain("0<>[]3456789", 1, 0, "0<[]3456789");
verifyDeleteSurroundingTextMain("0<>[]3456789", 2, 0, "0[]3456789");
verifyDeleteSurroundingTextMain("0<>[]3456789", 3, 0, "[]3456789");
verifyDeleteSurroundingTextMain("012[]<>56789", 0, 1, "012[]>56789");
verifyDeleteSurroundingTextMain("012[]<>56789", 0, 2, "012[]56789");
verifyDeleteSurroundingTextMain("012[]<>56789", 0, 3, "012[]6789");
verifyDeleteSurroundingTextMain("0<<[]3456789", 1, 0, "0<[]3456789");
verifyDeleteSurroundingTextMain("0<<[]3456789", 2, 0, "0[]3456789");
verifyDeleteSurroundingTextMain("0<<[]3456789", 3, 0, "[]3456789");
verifyDeleteSurroundingTextMain("012[]<<56789", 0, 1, "012[]<56789");
verifyDeleteSurroundingTextMain("012[]<<56789", 0, 2, "012[]56789");
verifyDeleteSurroundingTextMain("012[]<<56789", 0, 3, "012[]6789");
verifyDeleteSurroundingTextMain("0>>[]3456789", 1, 0, "0>[]3456789");
verifyDeleteSurroundingTextMain("0>>[]3456789", 2, 0, "0[]3456789");
verifyDeleteSurroundingTextMain("0>>[]3456789", 3, 0, "[]3456789");
verifyDeleteSurroundingTextMain("012[]>>56789", 0, 1, "012[]>56789");
verifyDeleteSurroundingTextMain("012[]>>56789", 0, 2, "012[]56789");
verifyDeleteSurroundingTextMain("012[]>>56789", 0, 3, "012[]6789");
}
private static void verifyDeleteSurroundingTextInCodePointsMain(String initialState,
int deleteBeforeInCodePoints, int deleteAfterInCodePoints, String expectedState) {
final CharSequence source = InputConnectionTestUtils.formatString(initialState);
final BaseInputConnection ic = createConnectionWithSelection(source);
ic.deleteSurroundingTextInCodePoints(deleteBeforeInCodePoints, deleteAfterInCodePoints);
final CharSequence expectedString = InputConnectionTestUtils.formatString(expectedState);
final int expectedSelectionStart = Selection.getSelectionStart(expectedString);
final int expectedSelectionEnd = Selection.getSelectionEnd(expectedString);
// It is sufficient to check the surrounding text up to source.length() characters, because
// InputConnection.deleteSurroundingTextInCodePoints() is not supposed to increase the text
// length.
final int retrievalLength = source.length();
if (expectedSelectionStart == 0) {
assertTrue(TextUtils.isEmpty(ic.getTextBeforeCursor(retrievalLength, 0)));
} else {
assertEquals(expectedString.subSequence(0, expectedSelectionStart).toString(),
ic.getTextBeforeCursor(retrievalLength, 0).toString());
}
if (expectedSelectionStart == expectedSelectionEnd) {
assertTrue(TextUtils.isEmpty(ic.getSelectedText(0))); // null is allowed.
} else {
assertEquals(expectedString.subSequence(expectedSelectionStart,
expectedSelectionEnd).toString(), ic.getSelectedText(0).toString());
}
if (expectedSelectionEnd == expectedString.length()) {
assertTrue(TextUtils.isEmpty(ic.getTextAfterCursor(retrievalLength, 0)));
} else {
assertEquals(expectedString.subSequence(expectedSelectionEnd,
expectedString.length()).toString(),
ic.getTextAfterCursor(retrievalLength, 0).toString());
}
}
/**
* Tests {@link BaseInputConnection#deleteSurroundingTextInCodePoints(int, int)}
* comprehensively.
*/
@Test
public void testDeleteSurroundingTextInCodePoints() {
verifyDeleteSurroundingTextInCodePointsMain("012[]3456789", 0, 0, "012[]3456789");
verifyDeleteSurroundingTextInCodePointsMain("012[]3456789", -1, -1, "012[]3456789");
verifyDeleteSurroundingTextInCodePointsMain("012[]3456789", 1, 2, "01[]56789");
verifyDeleteSurroundingTextInCodePointsMain("012[]3456789", 10, 1, "[]456789");
verifyDeleteSurroundingTextInCodePointsMain("012[]3456789", 1, 10, "01[]");
verifyDeleteSurroundingTextInCodePointsMain("[]0123456789", 3, 3, "[]3456789");
verifyDeleteSurroundingTextInCodePointsMain("0123456789[]", 3, 3, "0123456[]");
verifyDeleteSurroundingTextInCodePointsMain("012[345]6789", 0, 0, "012[345]6789");
verifyDeleteSurroundingTextInCodePointsMain("012[345]6789", -1, -1, "012[345]6789");
verifyDeleteSurroundingTextInCodePointsMain("012[345]6789", 1, 2, "01[345]89");
verifyDeleteSurroundingTextInCodePointsMain("012[345]6789", 10, 1, "[345]789");
verifyDeleteSurroundingTextInCodePointsMain("012[345]6789", 1, 10, "01[345]");
verifyDeleteSurroundingTextInCodePointsMain("[012]3456789", 3, 3, "[012]6789");
verifyDeleteSurroundingTextInCodePointsMain("0123456[789]", 3, 3, "0123[789]");
verifyDeleteSurroundingTextInCodePointsMain("[0123456789]", 0, 0, "[0123456789]");
verifyDeleteSurroundingTextInCodePointsMain("[0123456789]", 1, 1, "[0123456789]");
verifyDeleteSurroundingTextInCodePointsMain("0<>[]3456789", 1, 0, "0[]3456789");
verifyDeleteSurroundingTextInCodePointsMain("0<>[]3456789", 2, 0, "[]3456789");
verifyDeleteSurroundingTextInCodePointsMain("0<>[]3456789", 3, 0, "[]3456789");
verifyDeleteSurroundingTextInCodePointsMain("012[]<>56789", 0, 1, "012[]56789");
verifyDeleteSurroundingTextInCodePointsMain("012[]<>56789", 0, 2, "012[]6789");
verifyDeleteSurroundingTextInCodePointsMain("012[]<>56789", 0, 3, "012[]789");
verifyDeleteSurroundingTextInCodePointsMain("[]<><><><><>", 0, 0, "[]<><><><><>");
verifyDeleteSurroundingTextInCodePointsMain("[]<><><><><>", 0, 1, "[]<><><><>");
verifyDeleteSurroundingTextInCodePointsMain("[]<><><><><>", 0, 2, "[]<><><>");
verifyDeleteSurroundingTextInCodePointsMain("[]<><><><><>", 0, 3, "[]<><>");
verifyDeleteSurroundingTextInCodePointsMain("[]<><><><><>", 0, 4, "[]<>");
verifyDeleteSurroundingTextInCodePointsMain("[]<><><><><>", 0, 5, "[]");
verifyDeleteSurroundingTextInCodePointsMain("[]<><><><><>", 0, 6, "[]");
verifyDeleteSurroundingTextInCodePointsMain("[]<><><><><>", 0, 1000, "[]");
verifyDeleteSurroundingTextInCodePointsMain("<><><><><>[]", 0, 0, "<><><><><>[]");
verifyDeleteSurroundingTextInCodePointsMain("<><><><><>[]", 1, 0, "<><><><>[]");
verifyDeleteSurroundingTextInCodePointsMain("<><><><><>[]", 2, 0, "<><><>[]");
verifyDeleteSurroundingTextInCodePointsMain("<><><><><>[]", 3, 0, "<><>[]");
verifyDeleteSurroundingTextInCodePointsMain("<><><><><>[]", 4, 0, "<>[]");
verifyDeleteSurroundingTextInCodePointsMain("<><><><><>[]", 5, 0, "[]");
verifyDeleteSurroundingTextInCodePointsMain("<><><><><>[]", 6, 0, "[]");
verifyDeleteSurroundingTextInCodePointsMain("<><><><><>[]", 1000, 0, "[]");
verifyDeleteSurroundingTextInCodePointsMain("0<<[]3456789", 1, 0, "0<<[]3456789");
verifyDeleteSurroundingTextInCodePointsMain("0<<[]3456789", 2, 0, "0<<[]3456789");
verifyDeleteSurroundingTextInCodePointsMain("0<<[]3456789", 3, 0, "0<<[]3456789");
verifyDeleteSurroundingTextInCodePointsMain("012[]<<56789", 0, 1, "012[]<<56789");
verifyDeleteSurroundingTextInCodePointsMain("012[]<<56789", 0, 2, "012[]<<56789");
verifyDeleteSurroundingTextInCodePointsMain("012[]<<56789", 0, 3, "012[]<<56789");
verifyDeleteSurroundingTextInCodePointsMain("0>>[]3456789", 1, 0, "0>>[]3456789");
verifyDeleteSurroundingTextInCodePointsMain("0>>[]3456789", 2, 0, "0>>[]3456789");
verifyDeleteSurroundingTextInCodePointsMain("0>>[]3456789", 3, 0, "0>>[]3456789");
verifyDeleteSurroundingTextInCodePointsMain("012[]>>56789", 0, 1, "012[]>>56789");
verifyDeleteSurroundingTextInCodePointsMain("012[]>>56789", 0, 2, "012[]>>56789");
verifyDeleteSurroundingTextInCodePointsMain("012[]>>56789", 0, 3, "012[]>>56789");
verifyDeleteSurroundingTextInCodePointsMain("01<[]>456789", 1, 0, "01<[]>456789");
verifyDeleteSurroundingTextInCodePointsMain("01<[]>456789", 0, 1, "01<[]>456789");
verifyDeleteSurroundingTextInCodePointsMain("<12[]3456789", 1, 0, "<1[]3456789");
verifyDeleteSurroundingTextInCodePointsMain("<12[]3456789", 2, 0, "<[]3456789");
verifyDeleteSurroundingTextInCodePointsMain("<12[]3456789", 3, 0, "<12[]3456789");
verifyDeleteSurroundingTextInCodePointsMain("<<>[]3456789", 1, 0, "<[]3456789");
verifyDeleteSurroundingTextInCodePointsMain("<<>[]3456789", 2, 0, "<<>[]3456789");
verifyDeleteSurroundingTextInCodePointsMain("<<>[]3456789", 3, 0, "<<>[]3456789");
verifyDeleteSurroundingTextInCodePointsMain("012[]34>6789", 0, 1, "012[]4>6789");
verifyDeleteSurroundingTextInCodePointsMain("012[]34>6789", 0, 2, "012[]>6789");
verifyDeleteSurroundingTextInCodePointsMain("012[]34>6789", 0, 3, "012[]34>6789");
verifyDeleteSurroundingTextInCodePointsMain("012[]<>>6789", 0, 1, "012[]>6789");
verifyDeleteSurroundingTextInCodePointsMain("012[]<>>6789", 0, 2, "012[]<>>6789");
verifyDeleteSurroundingTextInCodePointsMain("012[]<>>6789", 0, 3, "012[]<>>6789");
// Atomicity test.
verifyDeleteSurroundingTextInCodePointsMain("0<<[]3456789", 1, 1, "0<<[]3456789");
verifyDeleteSurroundingTextInCodePointsMain("0<<[]3456789", 2, 1, "0<<[]3456789");
verifyDeleteSurroundingTextInCodePointsMain("0<<[]3456789", 3, 1, "0<<[]3456789");
verifyDeleteSurroundingTextInCodePointsMain("012[]<<56789", 1, 1, "012[]<<56789");
verifyDeleteSurroundingTextInCodePointsMain("012[]<<56789", 1, 2, "012[]<<56789");
verifyDeleteSurroundingTextInCodePointsMain("012[]<<56789", 1, 3, "012[]<<56789");
verifyDeleteSurroundingTextInCodePointsMain("0>>[]3456789", 1, 1, "0>>[]3456789");
verifyDeleteSurroundingTextInCodePointsMain("0>>[]3456789", 2, 1, "0>>[]3456789");
verifyDeleteSurroundingTextInCodePointsMain("0>>[]3456789", 3, 1, "0>>[]3456789");
verifyDeleteSurroundingTextInCodePointsMain("012[]>>56789", 1, 1, "012[]>>56789");
verifyDeleteSurroundingTextInCodePointsMain("012[]>>56789", 1, 2, "012[]>>56789");
verifyDeleteSurroundingTextInCodePointsMain("012[]>>56789", 1, 3, "012[]>>56789");
verifyDeleteSurroundingTextInCodePointsMain("01<[]>456789", 1, 1, "01<[]>456789");
// Do not verify the character sequences in the selected region.
verifyDeleteSurroundingTextInCodePointsMain("01[><]456789", 1, 0, "0[><]456789");
verifyDeleteSurroundingTextInCodePointsMain("01[><]456789", 0, 1, "01[><]56789");
verifyDeleteSurroundingTextInCodePointsMain("01[><]456789", 1, 1, "0[><]56789");
}
@Test
public void testCloseConnection() {
final BaseInputConnection connection = createBaseInputConnection();
final CharSequence source = "0123456789";
connection.commitText(source, source.length());
connection.setComposingRegion(2, 5);
final Editable text = connection.getEditable();
assertEquals(2, BaseInputConnection.getComposingSpanStart(text));
assertEquals(5, BaseInputConnection.getComposingSpanEnd(text));
// BaseInputConnection#closeConnection() must clear the on-going composition.
connection.closeConnection();
assertEquals(-1, BaseInputConnection.getComposingSpanStart(text));
assertEquals(-1, BaseInputConnection.getComposingSpanEnd(text));
}
@Test
public void testGetHandler() {
final BaseInputConnection connection = createBaseInputConnection();
// BaseInputConnection must not implement getHandler().
assertNull(connection.getHandler());
}
@Test
public void testCommitContent() {
final BaseInputConnection connection = createBaseInputConnection();
final InputContentInfo inputContentInfo = new InputContentInfo(
Uri.parse("content://com.example/path"),
new ClipDescription("sample content", new String[]{"image/png"}),
Uri.parse("https://example.com"));
// The default implementation should do nothing and just return false.
assertFalse(connection.commitContent(inputContentInfo, 0 /* flags */, null /* opts */));
}
@Test
public void testGetSelectedText_wrongSelection() {
final BaseInputConnection connection = createBaseInputConnection();
Editable editable = connection.getEditable();
editable.append("hello");
editable.setSpan(Selection.SELECTION_START, 4, 4, Spanned.SPAN_POINT_POINT);
editable.removeSpan(Selection.SELECTION_END);
// Should not crash.
connection.getSelectedText(0);
}
@Test
public void testGetSurroundingText_hasTextBeforeSelection() {
// 123456789|
final CharSequence source = InputConnectionTestUtils.formatString("123456789[]");
final BaseInputConnection connection = createConnectionWithSelection(source);
// 9|
SurroundingText surroundingText1 = connection.getSurroundingText(1, 1, 0);
assertEquals("9", surroundingText1.getText().toString());
assertEquals(1, surroundingText1.getSelectionEnd());
assertEquals(1, surroundingText1.getSelectionEnd());
assertEquals(8, surroundingText1.getOffset());
// 123456789|
SurroundingText surroundingText2 = connection.getSurroundingText(10, 1, 0);
assertEquals("123456789", surroundingText2.getText().toString());
assertEquals(9, surroundingText2.getSelectionStart());
assertEquals(9, surroundingText2.getSelectionEnd());
assertEquals(0, surroundingText2.getOffset());
// |
SurroundingText surroundingText3 = connection.getSurroundingText(0, 10,
BaseInputConnection.GET_TEXT_WITH_STYLES);
assertEquals("", surroundingText3.getText().toString());
assertEquals(0, surroundingText3.getSelectionStart());
assertEquals(0, surroundingText3.getSelectionEnd());
assertEquals(9, surroundingText3.getOffset());
}
@Test
public void testGetSurroundingText_hasTextAfterSelection() {
// |123456789
final CharSequence source = InputConnectionTestUtils.formatString("[]123456789");
final BaseInputConnection connection = createConnectionWithSelection(source);
// |1
SurroundingText surroundingText1 = connection.getSurroundingText(1, 1,
BaseInputConnection.GET_TEXT_WITH_STYLES);
assertEquals("1", surroundingText1.getText().toString());
assertEquals(0, surroundingText1.getSelectionStart());
assertEquals(0, surroundingText1.getSelectionEnd());
assertEquals(0, surroundingText1.getOffset());
// |
SurroundingText surroundingText2 = connection.getSurroundingText(10, 1, 0);
assertEquals("1", surroundingText2.getText().toString());
assertEquals(0, surroundingText2.getSelectionStart());
assertEquals(0, surroundingText2.getSelectionEnd());
assertEquals(0, surroundingText2.getOffset());
// |123456789
SurroundingText surroundingText3 = connection.getSurroundingText(0, 10, 0);
assertEquals("123456789", surroundingText3.getText().toString());
assertEquals(0, surroundingText3.getSelectionStart());
assertEquals(0, surroundingText3.getSelectionEnd());
assertEquals(0, surroundingText3.getOffset());
}
@Test
public void testGetSurroundingText_hasSelection() {
// 123|45|6789
final CharSequence source = InputConnectionTestUtils.formatString("123[45]6789");
final BaseInputConnection connection = createConnectionWithSelection(source);
// 3|45|6
SurroundingText surroundingText1 = connection.getSurroundingText(1, 1, 0);
assertEquals("3456", surroundingText1.getText().toString());
assertEquals(1, surroundingText1.getSelectionStart());
assertEquals(3, surroundingText1.getSelectionEnd());
assertEquals(2, surroundingText1.getOffset());
// 123|45|6
SurroundingText surroundingText2 = connection.getSurroundingText(10, 1,
BaseInputConnection.GET_TEXT_WITH_STYLES);
assertEquals("123456", surroundingText2.getText().toString());
assertEquals(3, surroundingText2.getSelectionStart());
assertEquals(5, surroundingText2.getSelectionEnd());
assertEquals(0, surroundingText2.getOffset());
// |45|6789
SurroundingText surroundingText3 = connection.getSurroundingText(0, 10, 0);
assertEquals("456789", surroundingText3.getText().toString());
assertEquals(0, surroundingText3.getSelectionStart());
assertEquals(2, surroundingText3.getSelectionEnd());
assertEquals(3, surroundingText3.getOffset());
// 123|45|6789
SurroundingText surroundingText4 = connection.getSurroundingText(10, 10,
BaseInputConnection.GET_TEXT_WITH_STYLES);
assertEquals("123456789", surroundingText4.getText().toString());
assertEquals(3, surroundingText4.getSelectionStart());
assertEquals(5, surroundingText4.getSelectionEnd());
assertEquals(0, surroundingText4.getOffset());
// |45|
SurroundingText surroundingText5 = connection.getSurroundingText(0, 0,
BaseInputConnection.GET_TEXT_WITH_STYLES);
assertEquals("45", surroundingText5.getText().toString());
assertEquals(0, surroundingText5.getSelectionStart());
assertEquals(2, surroundingText5.getSelectionEnd());
assertEquals(3, surroundingText5.getOffset());
}
@Test
public void testInvalidGetTextBeforeOrAfterCursorRequest() {
final CharSequence source = InputConnectionTestUtils.formatString("hello[]");
final BaseInputConnection connection = createConnectionWithSelection(source);
// getTextBeforeCursor
assertEquals("", connection.getTextBeforeCursor(0, 0).toString());
assertEquals("", connection.getTextBeforeCursor(
0, BaseInputConnection.GET_TEXT_WITH_STYLES).toString());
assertEquals("hello", connection.getTextBeforeCursor(10, 0).toString());
assertEquals("hello", connection.getTextBeforeCursor(
100, BaseInputConnection.GET_TEXT_WITH_STYLES).toString());
expectThrows(IllegalArgumentException.class, ()-> connection.getTextBeforeCursor(-1, 0));
// getTextAfterCursor
assertEquals("", connection.getTextAfterCursor(0, 0).toString());
assertEquals("", connection.getTextAfterCursor(
0, BaseInputConnection.GET_TEXT_WITH_STYLES).toString());
assertEquals("", connection.getTextAfterCursor(100, 0).toString());
assertEquals("", connection.getTextAfterCursor(
100, BaseInputConnection.GET_TEXT_WITH_STYLES).toString());
expectThrows(IllegalArgumentException.class, ()-> connection.getTextAfterCursor(-1, 0));
}
@Test
public void testTakeSnapshot() {
final BaseInputConnection connection = createConnectionWithSelection(
InputConnectionTestUtils.formatString("0123[456]789"));
verifyTextSnapshot(connection);
connection.setSelection(10, 10);
verifyTextSnapshot(connection);
connection.setComposingRegion(3, 10);
verifyTextSnapshot(connection);
connection.finishComposingText();
verifyTextSnapshot(connection);
}
@Test
public void testTakeSnapshotForNoSelection() {
final BaseInputConnection connection = createConnection(
Editable.Factory.getInstance().newEditable("test"));
// null should be returned for text with no selection.
assertThat(connection.takeSnapshot()).isNull();
}
private void verifyTextSnapshot(@NonNull BaseInputConnection connection) {
final Editable editable = connection.getEditable();
final TextSnapshot snapshot = connection.takeSnapshot();
assertThat(snapshot).isNotNull();
assertThat(snapshot.getSelectionStart()).isEqualTo(Selection.getSelectionStart(editable));
assertThat(snapshot.getSelectionEnd()).isEqualTo(Selection.getSelectionEnd(editable));
assertThat(snapshot.getCompositionStart())
.isEqualTo(BaseInputConnection.getComposingSpanStart(editable));
assertThat(snapshot.getCompositionEnd())
.isEqualTo(BaseInputConnection.getComposingSpanEnd(editable));
assertThat(snapshot.getCursorCapsMode()).isEqualTo(
connection.getCursorCapsMode(CAPS_MODE_MASK));
}
}