1 /*
2  * Copyright (C) 2008 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.view.inputmethod.cts;
18 
19 import static com.google.common.truth.Truth.assertThat;
20 
21 import static org.junit.Assert.assertEquals;
22 import static org.junit.Assert.assertFalse;
23 import static org.junit.Assert.assertNotNull;
24 import static org.junit.Assert.assertNull;
25 import static org.junit.Assert.assertTrue;
26 import static org.junit.Assert.fail;
27 import static org.testng.Assert.expectThrows;
28 
29 import android.content.ClipDescription;
30 import android.net.Uri;
31 import android.os.Bundle;
32 import android.platform.test.annotations.AppModeSdkSandbox;
33 import android.text.Editable;
34 import android.text.Selection;
35 import android.text.Spannable;
36 import android.text.SpannableString;
37 import android.text.Spanned;
38 import android.text.TextUtils;
39 import android.view.inputmethod.BaseInputConnection;
40 import android.view.inputmethod.CompletionInfo;
41 import android.view.inputmethod.ExtractedTextRequest;
42 import android.view.inputmethod.InputConnection;
43 import android.view.inputmethod.InputContentInfo;
44 import android.view.inputmethod.InputMethodManager;
45 import android.view.inputmethod.SurroundingText;
46 import android.view.inputmethod.TextSnapshot;
47 import android.view.inputmethod.cts.util.InputConnectionTestUtils;
48 
49 import androidx.annotation.NonNull;
50 import androidx.test.ext.junit.runners.AndroidJUnit4;
51 import androidx.test.filters.MediumTest;
52 import androidx.test.platform.app.InstrumentationRegistry;
53 
54 import org.junit.Test;
55 import org.junit.runner.RunWith;
56 
57 @MediumTest
58 @RunWith(AndroidJUnit4.class)
59 @AppModeSdkSandbox(reason = "Allow test in the SDK sandbox (does not prevent other modes).")
60 public class BaseInputConnectionTest {
61 
62     private static final int CAPS_MODE_MASK = TextUtils.CAP_MODE_CHARACTERS
63             | TextUtils.CAP_MODE_WORDS | TextUtils.CAP_MODE_SENTENCES;
64 
65     private static final int MEMORY_EFFICIENT_TEXT_LENGTH = 2048;
66 
67     // Retrieve a large range to text to verify the content.
68     private static final int TEXT_LENGTH_TO_RETRIEVAL = 1024;
69 
70     @Test
testDefaultMethods()71     public void testDefaultMethods() {
72         // These methods are default to return fixed result.
73         final BaseInputConnection connection = InputConnectionTestUtils.createBaseInputConnection();
74 
75         assertFalse(connection.beginBatchEdit());
76         assertFalse(connection.endBatchEdit());
77 
78         // only fit for test default implementation of commitCompletion.
79         int completionId = 1;
80         String completionString = "commitCompletion test";
81         assertFalse(connection.commitCompletion(new CompletionInfo(completionId,
82                 0, completionString)));
83 
84         assertNull(connection.getExtractedText(new ExtractedTextRequest(), 0));
85 
86         // only fit for test default implementation of performEditorAction.
87         int actionCode = 1;
88         int actionId = 2;
89         String action = "android.intent.action.MAIN";
90         assertTrue(connection.performEditorAction(actionCode));
91         assertFalse(connection.performContextMenuAction(actionId));
92         assertFalse(connection.performPrivateCommand(action, new Bundle()));
93     }
94 
95     @Test
testOpComposingSpans()96     public void testOpComposingSpans() {
97         Spannable text = new SpannableString("Test ComposingSpans");
98         BaseInputConnection.setComposingSpans(text);
99         assertTrue(BaseInputConnection.getComposingSpanStart(text) > -1);
100         assertTrue(BaseInputConnection.getComposingSpanEnd(text) > -1);
101         BaseInputConnection.removeComposingSpans(text);
102         assertTrue(BaseInputConnection.getComposingSpanStart(text) == -1);
103         assertTrue(BaseInputConnection.getComposingSpanEnd(text) == -1);
104     }
105 
106     /**
107      * getEditable: Return the target of edit operations. The default implementation
108      *              returns its own fake editable that is just used for composing text.
109      * clearMetaKeyStates: Default implementation uses
110      *              MetaKeyKeyListener#clearMetaKeyState(long, int) to clear the state.
111      *              BugId:1738511
112      * commitText: Default implementation replaces any existing composing text with the given
113      *             text.
114      * deleteSurroundingText: The default implementation performs the deletion around the current
115      *              selection position of the editable text.
116      * getCursorCapsMode: The default implementation uses TextUtils.getCapsMode to get the
117      *                  cursor caps mode for the current selection position in the editable text.
118      *                  TextUtils.getCapsMode is tested fully in TextUtilsTest#testGetCapsMode.
119      * getTextBeforeCursor, getTextAfterCursor: The default implementation performs the deletion
120      *                          around the current selection position of the editable text.
121      * setSelection: changes the selection position in the current editable text.
122      */
123     @Test
testOpTextMethods()124     public void testOpTextMethods() {
125         final BaseInputConnection connection = InputConnectionTestUtils.createBaseInputConnection();
126 
127         // return is an default Editable instance with empty source
128         final Editable text = connection.getEditable();
129         assertNotNull(text);
130         assertEquals(0, text.length());
131 
132         // Test commitText, not default fake mode
133         CharSequence str = "TestCommit ";
134         Editable inputText = Editable.Factory.getInstance().newEditable(str);
135         connection.commitText(inputText, inputText.length());
136         final Editable text2 = connection.getEditable();
137         int strLength = str.length();
138         assertEquals(strLength, text2.length());
139         assertEquals(str.toString(), text2.toString());
140         assertEquals(TextUtils.CAP_MODE_WORDS,
141                 connection.getCursorCapsMode(TextUtils.CAP_MODE_WORDS));
142         int offLength = 3;
143         CharSequence expected = str.subSequence(strLength - offLength, strLength);
144         assertEquals(expected.toString(), connection.getTextBeforeCursor(offLength,
145                 BaseInputConnection.GET_TEXT_WITH_STYLES).toString());
146         connection.setSelection(0, 0);
147         expected = str.subSequence(0, offLength);
148         assertEquals(expected.toString(), connection.getTextAfterCursor(offLength,
149                 BaseInputConnection.GET_TEXT_WITH_STYLES).toString());
150 
151         // Test deleteSurroundingText
152         int end = text2.length();
153         connection.setSelection(end, end);
154         // Delete the ending space
155         assertTrue(connection.deleteSurroundingText(1, 2));
156         Editable text3 = connection.getEditable();
157         assertEquals(strLength - 1, text3.length());
158         String expectedDelString = "TestCommit";
159         assertEquals(expectedDelString, text3.toString());
160     }
161 
162     /**
163      * finishComposingText: The default implementation removes the composing state from the
164      *                      current editable text.
165      * setComposingText: The default implementation places the given text into the editable,
166      *                  replacing any existing composing text
167      */
168     @Test
testFinishComposingText()169     public void testFinishComposingText() {
170         final BaseInputConnection connection = InputConnectionTestUtils.createBaseInputConnection();
171         CharSequence str = "TestFinish";
172         Editable inputText = Editable.Factory.getInstance().newEditable(str);
173         connection.commitText(inputText, inputText.length());
174         final Editable text = connection.getEditable();
175         // Test finishComposingText, not default fake mode
176         BaseInputConnection.setComposingSpans(text);
177         assertTrue(BaseInputConnection.getComposingSpanStart(text) > -1);
178         assertTrue(BaseInputConnection.getComposingSpanEnd(text) > -1);
179         connection.finishComposingText();
180         assertTrue(BaseInputConnection.getComposingSpanStart(text) == -1);
181         assertTrue(BaseInputConnection.getComposingSpanEnd(text) == -1);
182     }
183 
184     /**
185      * Updates InputMethodManager with the current fullscreen mode.
186      */
187     @Test
testReportFullscreenMode()188     public void testReportFullscreenMode() {
189         final InputMethodManager imm = InstrumentationRegistry.getInstrumentation()
190                 .getTargetContext().getSystemService(InputMethodManager.class);
191         final BaseInputConnection connection = InputConnectionTestUtils.createBaseInputConnection();
192         connection.reportFullscreenMode(false);
193         assertFalse(imm.isFullscreenMode());
194         connection.reportFullscreenMode(true);
195         // Only IMEs are allowed to report full-screen mode.  Calling this method from the
196         // application should have no effect.
197         assertFalse(imm.isFullscreenMode());
198     }
199 
verifyDeleteSurroundingTextMain( final String initialState, final int deleteBefore, final int deleteAfter, final String expectedState)200     private void verifyDeleteSurroundingTextMain(
201             final String initialState,
202             final int deleteBefore,
203             final int deleteAfter,
204             final String expectedState) {
205         verifyDeleteSurroundingTextMain(initialState, deleteBefore, deleteAfter, expectedState,
206                 false /* clearSelection */);
207     }
208 
verifyDeleteSurroundingTextMain( final String initialState, final int deleteBefore, final int deleteAfter, final String expectedState, final boolean clearSelection)209     private void verifyDeleteSurroundingTextMain(
210             final String initialState,
211             final int deleteBefore,
212             final int deleteAfter,
213             final String expectedState,
214             final boolean clearSelection) {
215         final CharSequence source = clearSelection ? initialState
216                 : InputConnectionTestUtils.formatString(initialState);
217         final BaseInputConnection ic =
218                 InputConnectionTestUtils.createBaseInputConnectionWithSelection(source);
219 
220         if (clearSelection) {
221             Selection.removeSelection(ic.getEditable());
222         }
223 
224         final boolean result = ic.deleteSurroundingText(deleteBefore, deleteAfter);
225         if (!result) {
226             assertEquals(expectedState, ic.getEditable().toString());
227             return;
228         } else if (clearSelection) {
229             fail("deleteSurroundingText should return false for invalid selection");
230         }
231 
232         final CharSequence expectedString =
233                 clearSelection
234                         ? expectedState
235                         : InputConnectionTestUtils.formatString(expectedState);
236         final int expectedSelectionStart = Selection.getSelectionStart(expectedString);
237         final int expectedSelectionEnd = Selection.getSelectionEnd(expectedString);
238         verifyTextAndSelection(ic, expectedString, expectedSelectionStart, expectedSelectionEnd);
239     }
240 
241     /**
242      * Tests {@link BaseInputConnection#deleteSurroundingText(int, int)} comprehensively.
243      */
244     @Test
testDeleteSurroundingText()245     public void testDeleteSurroundingText() {
246         verifyDeleteSurroundingTextMain("0123456789", 1, 2, "0123456789",
247                 true /* clearSelection*/);
248         verifyDeleteSurroundingTextMain("012[]3456789", 0, 0, "012[]3456789");
249         verifyDeleteSurroundingTextMain("012[]3456789", -1, -1, "012[]3456789");
250         verifyDeleteSurroundingTextMain("012[]3456789", 1, 2, "01[]56789");
251         verifyDeleteSurroundingTextMain("012[]3456789", 10, 1, "[]456789");
252         verifyDeleteSurroundingTextMain("012[]3456789", 1, 10, "01[]");
253         verifyDeleteSurroundingTextMain("[]0123456789", 3, 3, "[]3456789");
254         verifyDeleteSurroundingTextMain("0123456789[]", 3, 3, "0123456[]");
255         verifyDeleteSurroundingTextMain("012[345]6789", 0, 0, "012[345]6789");
256         verifyDeleteSurroundingTextMain("012[345]6789", -1, -1, "012[345]6789");
257         verifyDeleteSurroundingTextMain("012[345]6789", 1, 2, "01[345]89");
258         verifyDeleteSurroundingTextMain("012[345]6789", 10, 1, "[345]789");
259         verifyDeleteSurroundingTextMain("012[345]6789", 1, 10, "01[345]");
260         verifyDeleteSurroundingTextMain("[012]3456789", 3, 3, "[012]6789");
261         verifyDeleteSurroundingTextMain("0123456[789]", 3, 3, "0123[789]");
262         verifyDeleteSurroundingTextMain("[0123456789]", 0, 0, "[0123456789]");
263         verifyDeleteSurroundingTextMain("[0123456789]", 1, 1, "[0123456789]");
264 
265         // Surrogate characters do not have any special meanings.  Validating the character sequence
266         // is beyond the goal of this API.
267         verifyDeleteSurroundingTextMain("0<>[]3456789", 1, 0, "0<[]3456789");
268         verifyDeleteSurroundingTextMain("0<>[]3456789", 2, 0, "0[]3456789");
269         verifyDeleteSurroundingTextMain("0<>[]3456789", 3, 0, "[]3456789");
270         verifyDeleteSurroundingTextMain("012[]<>56789", 0, 1, "012[]>56789");
271         verifyDeleteSurroundingTextMain("012[]<>56789", 0, 2, "012[]56789");
272         verifyDeleteSurroundingTextMain("012[]<>56789", 0, 3, "012[]6789");
273         verifyDeleteSurroundingTextMain("0<<[]3456789", 1, 0, "0<[]3456789");
274         verifyDeleteSurroundingTextMain("0<<[]3456789", 2, 0, "0[]3456789");
275         verifyDeleteSurroundingTextMain("0<<[]3456789", 3, 0, "[]3456789");
276         verifyDeleteSurroundingTextMain("012[]<<56789", 0, 1, "012[]<56789");
277         verifyDeleteSurroundingTextMain("012[]<<56789", 0, 2, "012[]56789");
278         verifyDeleteSurroundingTextMain("012[]<<56789", 0, 3, "012[]6789");
279         verifyDeleteSurroundingTextMain("0>>[]3456789", 1, 0, "0>[]3456789");
280         verifyDeleteSurroundingTextMain("0>>[]3456789", 2, 0, "0[]3456789");
281         verifyDeleteSurroundingTextMain("0>>[]3456789", 3, 0, "[]3456789");
282         verifyDeleteSurroundingTextMain("012[]>>56789", 0, 1, "012[]>56789");
283         verifyDeleteSurroundingTextMain("012[]>>56789", 0, 2, "012[]56789");
284         verifyDeleteSurroundingTextMain("012[]>>56789", 0, 3, "012[]6789");
285     }
286 
verifyDeleteSurroundingTextInCodePointsMain( String initialState, int deleteBeforeInCodePoints, int deleteAfterInCodePoints, String expectedState)287     private void verifyDeleteSurroundingTextInCodePointsMain(
288             String initialState,
289             int deleteBeforeInCodePoints,
290             int deleteAfterInCodePoints,
291             String expectedState) {
292         final CharSequence source = InputConnectionTestUtils.formatString(initialState);
293         final BaseInputConnection ic =
294                 InputConnectionTestUtils.createBaseInputConnectionWithSelection(source);
295         ic.deleteSurroundingTextInCodePoints(deleteBeforeInCodePoints, deleteAfterInCodePoints);
296 
297         final CharSequence expectedString = InputConnectionTestUtils.formatString(expectedState);
298         final int expectedSelectionStart = Selection.getSelectionStart(expectedString);
299         final int expectedSelectionEnd = Selection.getSelectionEnd(expectedString);
300         verifyTextAndSelection(ic, expectedString, expectedSelectionStart, expectedSelectionEnd);
301     }
302 
303     /**
304      * Tests {@link BaseInputConnection#deleteSurroundingTextInCodePoints(int, int)}
305      * comprehensively.
306      */
307     @Test
testDeleteSurroundingTextInCodePoints()308     public void testDeleteSurroundingTextInCodePoints() {
309         verifyDeleteSurroundingTextInCodePointsMain("012[]3456789", 0, 0, "012[]3456789");
310         verifyDeleteSurroundingTextInCodePointsMain("012[]3456789", -1, -1, "012[]3456789");
311         verifyDeleteSurroundingTextInCodePointsMain("012[]3456789", 1, 2, "01[]56789");
312         verifyDeleteSurroundingTextInCodePointsMain("012[]3456789", 10, 1, "[]456789");
313         verifyDeleteSurroundingTextInCodePointsMain("012[]3456789", 1, 10, "01[]");
314         verifyDeleteSurroundingTextInCodePointsMain("[]0123456789", 3, 3, "[]3456789");
315         verifyDeleteSurroundingTextInCodePointsMain("0123456789[]", 3, 3, "0123456[]");
316         verifyDeleteSurroundingTextInCodePointsMain("012[345]6789", 0, 0, "012[345]6789");
317         verifyDeleteSurroundingTextInCodePointsMain("012[345]6789", -1, -1, "012[345]6789");
318         verifyDeleteSurroundingTextInCodePointsMain("012[345]6789", 1, 2, "01[345]89");
319         verifyDeleteSurroundingTextInCodePointsMain("012[345]6789", 10, 1, "[345]789");
320         verifyDeleteSurroundingTextInCodePointsMain("012[345]6789", 1, 10, "01[345]");
321         verifyDeleteSurroundingTextInCodePointsMain("[012]3456789", 3, 3, "[012]6789");
322         verifyDeleteSurroundingTextInCodePointsMain("0123456[789]", 3, 3, "0123[789]");
323         verifyDeleteSurroundingTextInCodePointsMain("[0123456789]", 0, 0, "[0123456789]");
324         verifyDeleteSurroundingTextInCodePointsMain("[0123456789]", 1, 1, "[0123456789]");
325 
326         verifyDeleteSurroundingTextInCodePointsMain("0<>[]3456789", 1, 0, "0[]3456789");
327         verifyDeleteSurroundingTextInCodePointsMain("0<>[]3456789", 2, 0, "[]3456789");
328         verifyDeleteSurroundingTextInCodePointsMain("0<>[]3456789", 3, 0, "[]3456789");
329         verifyDeleteSurroundingTextInCodePointsMain("012[]<>56789", 0, 1, "012[]56789");
330         verifyDeleteSurroundingTextInCodePointsMain("012[]<>56789", 0, 2, "012[]6789");
331         verifyDeleteSurroundingTextInCodePointsMain("012[]<>56789", 0, 3, "012[]789");
332 
333         verifyDeleteSurroundingTextInCodePointsMain("[]<><><><><>", 0, 0, "[]<><><><><>");
334         verifyDeleteSurroundingTextInCodePointsMain("[]<><><><><>", 0, 1, "[]<><><><>");
335         verifyDeleteSurroundingTextInCodePointsMain("[]<><><><><>", 0, 2, "[]<><><>");
336         verifyDeleteSurroundingTextInCodePointsMain("[]<><><><><>", 0, 3, "[]<><>");
337         verifyDeleteSurroundingTextInCodePointsMain("[]<><><><><>", 0, 4, "[]<>");
338         verifyDeleteSurroundingTextInCodePointsMain("[]<><><><><>", 0, 5, "[]");
339         verifyDeleteSurroundingTextInCodePointsMain("[]<><><><><>", 0, 6, "[]");
340         verifyDeleteSurroundingTextInCodePointsMain("[]<><><><><>", 0, 1000, "[]");
341         verifyDeleteSurroundingTextInCodePointsMain("<><><><><>[]", 0, 0, "<><><><><>[]");
342         verifyDeleteSurroundingTextInCodePointsMain("<><><><><>[]", 1, 0, "<><><><>[]");
343         verifyDeleteSurroundingTextInCodePointsMain("<><><><><>[]", 2, 0, "<><><>[]");
344         verifyDeleteSurroundingTextInCodePointsMain("<><><><><>[]", 3, 0, "<><>[]");
345         verifyDeleteSurroundingTextInCodePointsMain("<><><><><>[]", 4, 0, "<>[]");
346         verifyDeleteSurroundingTextInCodePointsMain("<><><><><>[]", 5, 0, "[]");
347         verifyDeleteSurroundingTextInCodePointsMain("<><><><><>[]", 6, 0, "[]");
348         verifyDeleteSurroundingTextInCodePointsMain("<><><><><>[]", 1000, 0, "[]");
349 
350         verifyDeleteSurroundingTextInCodePointsMain("0<<[]3456789", 1, 0, "0<<[]3456789");
351         verifyDeleteSurroundingTextInCodePointsMain("0<<[]3456789", 2, 0, "0<<[]3456789");
352         verifyDeleteSurroundingTextInCodePointsMain("0<<[]3456789", 3, 0, "0<<[]3456789");
353         verifyDeleteSurroundingTextInCodePointsMain("012[]<<56789", 0, 1, "012[]<<56789");
354         verifyDeleteSurroundingTextInCodePointsMain("012[]<<56789", 0, 2, "012[]<<56789");
355         verifyDeleteSurroundingTextInCodePointsMain("012[]<<56789", 0, 3, "012[]<<56789");
356         verifyDeleteSurroundingTextInCodePointsMain("0>>[]3456789", 1, 0, "0>>[]3456789");
357         verifyDeleteSurroundingTextInCodePointsMain("0>>[]3456789", 2, 0, "0>>[]3456789");
358         verifyDeleteSurroundingTextInCodePointsMain("0>>[]3456789", 3, 0, "0>>[]3456789");
359         verifyDeleteSurroundingTextInCodePointsMain("012[]>>56789", 0, 1, "012[]>>56789");
360         verifyDeleteSurroundingTextInCodePointsMain("012[]>>56789", 0, 2, "012[]>>56789");
361         verifyDeleteSurroundingTextInCodePointsMain("012[]>>56789", 0, 3, "012[]>>56789");
362         verifyDeleteSurroundingTextInCodePointsMain("01<[]>456789", 1, 0, "01<[]>456789");
363         verifyDeleteSurroundingTextInCodePointsMain("01<[]>456789", 0, 1, "01<[]>456789");
364         verifyDeleteSurroundingTextInCodePointsMain("<12[]3456789", 1, 0, "<1[]3456789");
365         verifyDeleteSurroundingTextInCodePointsMain("<12[]3456789", 2, 0, "<[]3456789");
366         verifyDeleteSurroundingTextInCodePointsMain("<12[]3456789", 3, 0, "<12[]3456789");
367         verifyDeleteSurroundingTextInCodePointsMain("<<>[]3456789", 1, 0, "<[]3456789");
368         verifyDeleteSurroundingTextInCodePointsMain("<<>[]3456789", 2, 0, "<<>[]3456789");
369         verifyDeleteSurroundingTextInCodePointsMain("<<>[]3456789", 3, 0, "<<>[]3456789");
370         verifyDeleteSurroundingTextInCodePointsMain("012[]34>6789", 0, 1, "012[]4>6789");
371         verifyDeleteSurroundingTextInCodePointsMain("012[]34>6789", 0, 2, "012[]>6789");
372         verifyDeleteSurroundingTextInCodePointsMain("012[]34>6789", 0, 3, "012[]34>6789");
373         verifyDeleteSurroundingTextInCodePointsMain("012[]<>>6789", 0, 1, "012[]>6789");
374         verifyDeleteSurroundingTextInCodePointsMain("012[]<>>6789", 0, 2, "012[]<>>6789");
375         verifyDeleteSurroundingTextInCodePointsMain("012[]<>>6789", 0, 3, "012[]<>>6789");
376 
377         // Atomicity test.
378         verifyDeleteSurroundingTextInCodePointsMain("0<<[]3456789", 1, 1, "0<<[]3456789");
379         verifyDeleteSurroundingTextInCodePointsMain("0<<[]3456789", 2, 1, "0<<[]3456789");
380         verifyDeleteSurroundingTextInCodePointsMain("0<<[]3456789", 3, 1, "0<<[]3456789");
381         verifyDeleteSurroundingTextInCodePointsMain("012[]<<56789", 1, 1, "012[]<<56789");
382         verifyDeleteSurroundingTextInCodePointsMain("012[]<<56789", 1, 2, "012[]<<56789");
383         verifyDeleteSurroundingTextInCodePointsMain("012[]<<56789", 1, 3, "012[]<<56789");
384         verifyDeleteSurroundingTextInCodePointsMain("0>>[]3456789", 1, 1, "0>>[]3456789");
385         verifyDeleteSurroundingTextInCodePointsMain("0>>[]3456789", 2, 1, "0>>[]3456789");
386         verifyDeleteSurroundingTextInCodePointsMain("0>>[]3456789", 3, 1, "0>>[]3456789");
387         verifyDeleteSurroundingTextInCodePointsMain("012[]>>56789", 1, 1, "012[]>>56789");
388         verifyDeleteSurroundingTextInCodePointsMain("012[]>>56789", 1, 2, "012[]>>56789");
389         verifyDeleteSurroundingTextInCodePointsMain("012[]>>56789", 1, 3, "012[]>>56789");
390         verifyDeleteSurroundingTextInCodePointsMain("01<[]>456789", 1, 1, "01<[]>456789");
391 
392         // Do not verify the character sequences in the selected region.
393         verifyDeleteSurroundingTextInCodePointsMain("01[><]456789", 1, 0, "0[><]456789");
394         verifyDeleteSurroundingTextInCodePointsMain("01[><]456789", 0, 1, "01[><]56789");
395         verifyDeleteSurroundingTextInCodePointsMain("01[><]456789", 1, 1, "0[><]56789");
396     }
397 
398     @Test
testCloseConnection()399     public void testCloseConnection() {
400         final BaseInputConnection connection = InputConnectionTestUtils.createBaseInputConnection();
401 
402         final CharSequence source = "0123456789";
403         connection.commitText(source, source.length());
404         connection.setComposingRegion(2, 5);
405         final Editable text = connection.getEditable();
406         assertEquals(2, BaseInputConnection.getComposingSpanStart(text));
407         assertEquals(5, BaseInputConnection.getComposingSpanEnd(text));
408 
409         // BaseInputConnection#closeConnection() must clear the on-going composition.
410         connection.closeConnection();
411         assertEquals(-1, BaseInputConnection.getComposingSpanStart(text));
412         assertEquals(-1, BaseInputConnection.getComposingSpanEnd(text));
413     }
414 
415     @Test
testGetHandler()416     public void testGetHandler() {
417         final BaseInputConnection connection = InputConnectionTestUtils.createBaseInputConnection();
418 
419         // BaseInputConnection must not implement getHandler().
420         assertNull(connection.getHandler());
421     }
422 
423     @Test
testCommitContent()424     public void testCommitContent() {
425         final BaseInputConnection connection = InputConnectionTestUtils.createBaseInputConnection();
426 
427         final InputContentInfo inputContentInfo =
428                 new InputContentInfo(
429                         Uri.parse("content://com.example/path"),
430                         new ClipDescription("sample content", new String[] {"image/png"}),
431                         Uri.parse("https://example.com"));
432         // The default implementation should do nothing and just return false.
433         assertFalse(connection.commitContent(inputContentInfo, 0 /* flags */, null /* opts */));
434     }
435 
436     @Test
testGetSelectedText_wrongSelection()437     public void testGetSelectedText_wrongSelection() {
438         final BaseInputConnection connection = InputConnectionTestUtils.createBaseInputConnection();
439         Editable editable = connection.getEditable();
440         editable.append("hello");
441         editable.setSpan(Selection.SELECTION_START, 4, 4, Spanned.SPAN_POINT_POINT);
442         editable.removeSpan(Selection.SELECTION_END);
443 
444         // Should not crash.
445         connection.getSelectedText(0);
446     }
447 
448     @Test
testGetSurroundingText_hasTextBeforeSelection()449     public void testGetSurroundingText_hasTextBeforeSelection() {
450         // 123456789|
451         final CharSequence source = InputConnectionTestUtils.formatString("123456789[]");
452         final BaseInputConnection connection =
453                 InputConnectionTestUtils.createBaseInputConnectionWithSelection(source);
454 
455         // 9|
456         SurroundingText surroundingText1 = connection.getSurroundingText(1, 1, 0);
457         assertEquals("9", surroundingText1.getText().toString());
458         assertEquals(1, surroundingText1.getSelectionEnd());
459         assertEquals(1, surroundingText1.getSelectionEnd());
460         assertEquals(8, surroundingText1.getOffset());
461 
462         // 123456789|
463         SurroundingText surroundingText2 = connection.getSurroundingText(10, 1, 0);
464         assertEquals("123456789", surroundingText2.getText().toString());
465         assertEquals(9, surroundingText2.getSelectionStart());
466         assertEquals(9, surroundingText2.getSelectionEnd());
467         assertEquals(0, surroundingText2.getOffset());
468 
469         // |
470         SurroundingText surroundingText3 = connection.getSurroundingText(0, 10,
471                 BaseInputConnection.GET_TEXT_WITH_STYLES);
472         assertEquals("", surroundingText3.getText().toString());
473         assertEquals(0, surroundingText3.getSelectionStart());
474         assertEquals(0, surroundingText3.getSelectionEnd());
475         assertEquals(9, surroundingText3.getOffset());
476     }
477 
478     @Test
testGetSurroundingText_hasTextAfterSelection()479     public void testGetSurroundingText_hasTextAfterSelection() {
480         // |123456789
481         final CharSequence source = InputConnectionTestUtils.formatString("[]123456789");
482         final BaseInputConnection connection =
483                 InputConnectionTestUtils.createBaseInputConnectionWithSelection(source);
484 
485         // |1
486         SurroundingText surroundingText1 = connection.getSurroundingText(1, 1,
487                 BaseInputConnection.GET_TEXT_WITH_STYLES);
488         assertEquals("1", surroundingText1.getText().toString());
489         assertEquals(0, surroundingText1.getSelectionStart());
490         assertEquals(0, surroundingText1.getSelectionEnd());
491         assertEquals(0, surroundingText1.getOffset());
492 
493         // |
494         SurroundingText surroundingText2 = connection.getSurroundingText(10, 1, 0);
495         assertEquals("1", surroundingText2.getText().toString());
496         assertEquals(0, surroundingText2.getSelectionStart());
497         assertEquals(0, surroundingText2.getSelectionEnd());
498         assertEquals(0, surroundingText2.getOffset());
499 
500         // |123456789
501         SurroundingText surroundingText3 = connection.getSurroundingText(0, 10, 0);
502         assertEquals("123456789", surroundingText3.getText().toString());
503         assertEquals(0, surroundingText3.getSelectionStart());
504         assertEquals(0, surroundingText3.getSelectionEnd());
505         assertEquals(0, surroundingText3.getOffset());
506     }
507 
508     @Test
testGetSurroundingText_hasSelection()509     public void testGetSurroundingText_hasSelection() {
510         // 123|45|6789
511         final CharSequence source = InputConnectionTestUtils.formatString("123[45]6789");
512         final BaseInputConnection connection =
513                 InputConnectionTestUtils.createBaseInputConnectionWithSelection(source);
514 
515         // 3|45|6
516         SurroundingText surroundingText1 = connection.getSurroundingText(1, 1, 0);
517         assertEquals("3456", surroundingText1.getText().toString());
518         assertEquals(1, surroundingText1.getSelectionStart());
519         assertEquals(3, surroundingText1.getSelectionEnd());
520         assertEquals(2, surroundingText1.getOffset());
521 
522         // 123|45|6
523         SurroundingText surroundingText2 = connection.getSurroundingText(10, 1,
524                 BaseInputConnection.GET_TEXT_WITH_STYLES);
525         assertEquals("123456", surroundingText2.getText().toString());
526         assertEquals(3, surroundingText2.getSelectionStart());
527         assertEquals(5, surroundingText2.getSelectionEnd());
528         assertEquals(0, surroundingText2.getOffset());
529 
530         // |45|6789
531         SurroundingText surroundingText3 = connection.getSurroundingText(0, 10, 0);
532         assertEquals("456789", surroundingText3.getText().toString());
533         assertEquals(0, surroundingText3.getSelectionStart());
534         assertEquals(2, surroundingText3.getSelectionEnd());
535         assertEquals(3, surroundingText3.getOffset());
536 
537         // 123|45|6789
538         SurroundingText surroundingText4 = connection.getSurroundingText(10, 10,
539                 BaseInputConnection.GET_TEXT_WITH_STYLES);
540         assertEquals("123456789", surroundingText4.getText().toString());
541         assertEquals(3, surroundingText4.getSelectionStart());
542         assertEquals(5, surroundingText4.getSelectionEnd());
543         assertEquals(0, surroundingText4.getOffset());
544 
545         // |45|
546         SurroundingText surroundingText5 =
547                 connection.getSurroundingText(0, 0, BaseInputConnection.GET_TEXT_WITH_STYLES);
548         assertEquals("45", surroundingText5.getText().toString());
549         assertEquals(0, surroundingText5.getSelectionStart());
550         assertEquals(2, surroundingText5.getSelectionEnd());
551         assertEquals(3, surroundingText5.getOffset());
552     }
553 
554     @Test
testInvalidGetTextBeforeOrAfterCursorRequest()555     public void testInvalidGetTextBeforeOrAfterCursorRequest() {
556         final CharSequence source = InputConnectionTestUtils.formatString("hello[]");
557         final BaseInputConnection connection =
558                 InputConnectionTestUtils.createBaseInputConnectionWithSelection(source);
559 
560         // getTextBeforeCursor
561         assertEquals("", connection.getTextBeforeCursor(0, 0).toString());
562         assertEquals("", connection.getTextBeforeCursor(
563                 0, BaseInputConnection.GET_TEXT_WITH_STYLES).toString());
564         assertEquals("hello", connection.getTextBeforeCursor(10, 0).toString());
565         assertEquals("hello", connection.getTextBeforeCursor(
566                 100, BaseInputConnection.GET_TEXT_WITH_STYLES).toString());
567         expectThrows(IllegalArgumentException.class, ()-> connection.getTextBeforeCursor(-1, 0));
568 
569         // getTextAfterCursor
570         assertEquals("", connection.getTextAfterCursor(0, 0).toString());
571         assertEquals("", connection.getTextAfterCursor(
572                 0, BaseInputConnection.GET_TEXT_WITH_STYLES).toString());
573         assertEquals("", connection.getTextAfterCursor(100, 0).toString());
574         assertEquals("", connection.getTextAfterCursor(
575                 100, BaseInputConnection.GET_TEXT_WITH_STYLES).toString());
576         expectThrows(IllegalArgumentException.class, ()-> connection.getTextAfterCursor(-1, 0));
577     }
578 
579     @Test
testTakeSnapshot()580     public void testTakeSnapshot() {
581         final BaseInputConnection connection =
582                 InputConnectionTestUtils.createBaseInputConnectionWithSelection(
583                         InputConnectionTestUtils.formatString("0123[456]789"));
584 
585         verifyTextSnapshot(connection);
586 
587         connection.setSelection(10, 10);
588         verifyTextSnapshot(connection);
589 
590         connection.setComposingRegion(3, 10);
591         verifyTextSnapshot(connection);
592 
593         connection.finishComposingText();
594         verifyTextSnapshot(connection);
595     }
596 
597     @Test
testTakeSnapshotForNoSelection()598     public void testTakeSnapshotForNoSelection() {
599         final BaseInputConnection connection =
600                 InputConnectionTestUtils.createBaseInputConnection(
601                         Editable.Factory.getInstance().newEditable("test"));
602         // null should be returned for text with no selection.
603         assertThat(connection.takeSnapshot()).isNull();
604     }
605 
verifyTextSnapshot(@onNull BaseInputConnection connection)606     private void verifyTextSnapshot(@NonNull BaseInputConnection connection) {
607         final Editable editable = connection.getEditable();
608 
609         final TextSnapshot snapshot = connection.takeSnapshot();
610         assertThat(snapshot).isNotNull();
611         assertThat(snapshot.getSelectionStart()).isEqualTo(Selection.getSelectionStart(editable));
612         assertThat(snapshot.getSelectionEnd()).isEqualTo(Selection.getSelectionEnd(editable));
613         assertThat(snapshot.getCompositionStart())
614                 .isEqualTo(BaseInputConnection.getComposingSpanStart(editable));
615         assertThat(snapshot.getCompositionEnd())
616                 .isEqualTo(BaseInputConnection.getComposingSpanEnd(editable));
617         assertThat(snapshot.getCursorCapsMode())
618                 .isEqualTo(connection.getCursorCapsMode(CAPS_MODE_MASK));
619         final SurroundingText surroundingText = snapshot.getSurroundingText();
620         assertThat(surroundingText).isNotNull();
621         final SurroundingText expectedSurroundingText =
622                 connection.getSurroundingText(
623                         MEMORY_EFFICIENT_TEXT_LENGTH / 2,
624                         MEMORY_EFFICIENT_TEXT_LENGTH / 2,
625                         InputConnection.GET_TEXT_WITH_STYLES);
626         assertThat(
627                         surroundingText
628                                 .getText()
629                                 .toString()
630                                 .contentEquals(expectedSurroundingText.getText()))
631                 .isTrue();
632         assertThat(surroundingText.getSelectionStart())
633                 .isEqualTo(expectedSurroundingText.getSelectionStart());
634         assertThat(surroundingText.getSelectionStart())
635                 .isEqualTo(expectedSurroundingText.getSelectionStart());
636         assertThat(surroundingText.getSelectionEnd())
637                 .isEqualTo(expectedSurroundingText.getSelectionEnd());
638         assertThat(surroundingText.getOffset()).isEqualTo(expectedSurroundingText.getOffset());
639         assertThat(snapshot.getCursorCapsMode())
640                 .isEqualTo(connection.getCursorCapsMode(CAPS_MODE_MASK));
641     }
642 
643     @Test
testReplaceText()644     public void testReplaceText() {
645         verifyReplaceText("012[3456]789", 3, 7, "text", 1, "012text[]789");
646         verifyReplaceText("012[]3456789", 0, 3, "text", 1, "text[]3456789");
647         verifyReplaceText("012[]3456789", 3, 0, "text", 1, "text[]3456789");
648         verifyReplaceText("012[]3456789", 0, 10, "text", 1, "text[]");
649         verifyReplaceText("0123456789[]", 0, 3, "text", -1, "[]text3456789");
650         verifyReplaceText("0123456789[]", 10, 10, "text", 1, "0123456789text[]");
651         verifyReplaceText("0123456789[]", 100, 100, "text", 1, "0123456789text[]");
652         verifyReplaceText("[]0123456789", 0, 0, "text", 1, "text[]0123456789");
653         verifyReplaceText("[]0123456789", 0, 5, "text", 1, "text[]56789");
654         verifyReplaceText("[]0123456789", 0, 10, "text", -1, "[]text");
655         verifyReplaceText("[0123456789]", 0, 10, "text", 1, "text[]");
656         verifyReplaceText("[0123456789]", 0, 8, "text", 1, "text[]89");
657     }
658 
verifyReplaceText( final String initialState, final int start, final int end, final String text, final int newCursorPosition, final String expectedState)659     private static void verifyReplaceText(
660             final String initialState,
661             final int start,
662             final int end,
663             final String text,
664             final int newCursorPosition,
665             final String expectedState) {
666         final CharSequence source = InputConnectionTestUtils.formatString(initialState);
667         final BaseInputConnection ic =
668                 InputConnectionTestUtils.createBaseInputConnectionWithSelection(source);
669         assertTrue(ic.replaceText(start, end, text, newCursorPosition, null));
670         final CharSequence expectedString = InputConnectionTestUtils.formatString(expectedState);
671         final int expectedSelectionStart = Selection.getSelectionStart(expectedString);
672         final int expectedSelectionEnd = Selection.getSelectionEnd(expectedString);
673         verifyTextAndSelection(ic, expectedString, expectedSelectionStart, expectedSelectionEnd);
674     }
675 
verifyTextAndSelection( BaseInputConnection ic, final CharSequence expectedString, final int expectedSelectionStart, final int expectedSelectionEnd)676     private static void verifyTextAndSelection(
677             BaseInputConnection ic,
678             final CharSequence expectedString,
679             final int expectedSelectionStart,
680             final int expectedSelectionEnd) {
681         if (expectedSelectionStart == 0) {
682             assertTrue(TextUtils.isEmpty(ic.getTextBeforeCursor(TEXT_LENGTH_TO_RETRIEVAL, 0)));
683         } else {
684             assertEquals(
685                     expectedString.subSequence(0, expectedSelectionStart).toString(),
686                     ic.getTextBeforeCursor(TEXT_LENGTH_TO_RETRIEVAL, 0).toString());
687         }
688         if (expectedSelectionStart == expectedSelectionEnd) {
689             assertTrue(TextUtils.isEmpty(ic.getSelectedText(0))); // null is allowed.
690         } else {
691             assertEquals(
692                     expectedString
693                             .subSequence(expectedSelectionStart, expectedSelectionEnd)
694                             .toString(),
695                     ic.getSelectedText(0).toString());
696         }
697         if (expectedSelectionEnd == expectedString.length()) {
698             assertTrue(TextUtils.isEmpty(ic.getTextAfterCursor(TEXT_LENGTH_TO_RETRIEVAL, 0)));
699         } else {
700             assertEquals(
701                     expectedString
702                             .subSequence(expectedSelectionEnd, expectedString.length())
703                             .toString(),
704                     ic.getTextAfterCursor(TEXT_LENGTH_TO_RETRIEVAL, 0).toString());
705         }
706     }
707 }
708