RichInputConnectionAndTextRangeTests.java revision 914078fd9198aeb3d7ffa034562321d688d588f7
1/*
2 * Copyright (C) 2012 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
17package com.android.inputmethod.latin;
18
19import android.content.res.Resources;
20import android.inputmethodservice.InputMethodService;
21import android.os.Parcel;
22import android.test.AndroidTestCase;
23import android.test.MoreAsserts;
24import android.test.suitebuilder.annotation.SmallTest;
25import android.text.SpannableString;
26import android.text.TextUtils;
27import android.text.style.SuggestionSpan;
28import android.view.inputmethod.ExtractedText;
29import android.view.inputmethod.ExtractedTextRequest;
30import android.view.inputmethod.InputConnection;
31import android.view.inputmethod.InputConnectionWrapper;
32
33import com.android.inputmethod.latin.PrevWordsInfo.WordInfo;
34import com.android.inputmethod.latin.settings.SpacingAndPunctuations;
35import com.android.inputmethod.latin.utils.PrevWordsInfoUtils;
36import com.android.inputmethod.latin.utils.RunInLocale;
37import com.android.inputmethod.latin.utils.ScriptUtils;
38import com.android.inputmethod.latin.utils.StringUtils;
39import com.android.inputmethod.latin.utils.TextRange;
40
41import java.util.Locale;
42
43@SmallTest
44public class RichInputConnectionAndTextRangeTests extends AndroidTestCase {
45
46    // The following is meant to be a reasonable default for
47    // the "word_separators" resource.
48    private SpacingAndPunctuations mSpacingAndPunctuations;
49
50    @Override
51    protected void setUp() throws Exception {
52        super.setUp();
53        final RunInLocale<SpacingAndPunctuations> job = new RunInLocale<SpacingAndPunctuations>() {
54            @Override
55            protected SpacingAndPunctuations job(final Resources res) {
56                return new SpacingAndPunctuations(res);
57            }
58        };
59        final Resources res = getContext().getResources();
60        mSpacingAndPunctuations = job.runInLocale(res, Locale.ENGLISH);
61    }
62
63    private class MockConnection extends InputConnectionWrapper {
64        final CharSequence mTextBefore;
65        final CharSequence mTextAfter;
66        final ExtractedText mExtractedText;
67
68        public MockConnection(final CharSequence text, final int cursorPosition) {
69            super(null, false);
70            // Interaction of spans with Parcels is completely non-trivial, but in the actual case
71            // the CharSequences do go through Parcels because they go through IPC. There
72            // are some significant differences between the behavior of Spanned objects that
73            // have and that have not gone through parceling, so it's much easier to simulate
74            // the environment with Parcels than try to emulate things by hand.
75            final Parcel p = Parcel.obtain();
76            TextUtils.writeToParcel(text.subSequence(0, cursorPosition), p, 0 /* flags */);
77            TextUtils.writeToParcel(text.subSequence(cursorPosition, text.length()), p,
78                    0 /* flags */);
79            final byte[] marshalled = p.marshall();
80            p.unmarshall(marshalled, 0, marshalled.length);
81            p.setDataPosition(0);
82            mTextBefore = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(p);
83            mTextAfter = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(p);
84            mExtractedText = null;
85            p.recycle();
86        }
87
88        public MockConnection(String textBefore, String textAfter, ExtractedText extractedText) {
89            super(null, false);
90            mTextBefore = textBefore;
91            mTextAfter = textAfter;
92            mExtractedText = extractedText;
93        }
94
95        public int cursorPos() {
96            return mTextBefore.length();
97        }
98
99        /* (non-Javadoc)
100         * @see android.view.inputmethod.InputConnectionWrapper#getTextBeforeCursor(int, int)
101         */
102        @Override
103        public CharSequence getTextBeforeCursor(int n, int flags) {
104            return mTextBefore;
105        }
106
107        /* (non-Javadoc)
108         * @see android.view.inputmethod.InputConnectionWrapper#getTextAfterCursor(int, int)
109         */
110        @Override
111        public CharSequence getTextAfterCursor(int n, int flags) {
112            return mTextAfter;
113        }
114
115        /* (non-Javadoc)
116         * @see android.view.inputmethod.InputConnectionWrapper#getExtractedText(
117         *         ExtractedTextRequest, int)
118         */
119        @Override
120        public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
121            return mExtractedText;
122        }
123
124        @Override
125        public boolean beginBatchEdit() {
126            return true;
127        }
128
129        @Override
130        public boolean endBatchEdit() {
131            return true;
132        }
133
134        @Override
135        public boolean finishComposingText() {
136            return true;
137        }
138    }
139
140    private class MockInputMethodService extends InputMethodService {
141        private MockConnection mMockConnection;
142        public void setInputConnection(final MockConnection mockConnection) {
143            mMockConnection = mockConnection;
144        }
145        public int cursorPos() {
146            return mMockConnection.cursorPos();
147        }
148        @Override
149        public InputConnection getCurrentInputConnection() {
150            return mMockConnection;
151        }
152    }
153
154    /************************** Tests ************************/
155
156    /**
157     * Test for getting previous word (for bigram suggestions)
158     */
159    public void testGetPreviousWord() {
160        // If one of the following cases breaks, the bigram suggestions won't work.
161        assertEquals(PrevWordsInfoUtils.getPrevWordsInfoFromNthPreviousWord(
162                "abc def", mSpacingAndPunctuations, 2).mPrevWordsInfo[0].mWord, "abc");
163        assertEquals(PrevWordsInfoUtils.getPrevWordsInfoFromNthPreviousWord(
164                "abc", mSpacingAndPunctuations, 2), PrevWordsInfo.BEGINNING_OF_SENTENCE);
165        assertEquals(PrevWordsInfoUtils.getPrevWordsInfoFromNthPreviousWord(
166                "abc. def", mSpacingAndPunctuations, 2), PrevWordsInfo.BEGINNING_OF_SENTENCE);
167
168        assertFalse(PrevWordsInfoUtils.getPrevWordsInfoFromNthPreviousWord(
169                "abc def", mSpacingAndPunctuations, 2).mPrevWordsInfo[0].mIsBeginningOfSentence);
170        assertTrue(PrevWordsInfoUtils.getPrevWordsInfoFromNthPreviousWord(
171                "abc", mSpacingAndPunctuations, 2).mPrevWordsInfo[0].mIsBeginningOfSentence);
172
173        // For n-gram
174        assertEquals(PrevWordsInfoUtils.getPrevWordsInfoFromNthPreviousWord(
175                "abc def", mSpacingAndPunctuations, 1).mPrevWordsInfo[0].mWord, "def");
176        assertEquals(PrevWordsInfoUtils.getPrevWordsInfoFromNthPreviousWord(
177                "abc def", mSpacingAndPunctuations, 1).mPrevWordsInfo[1].mWord, "abc");
178        assertEquals(PrevWordsInfoUtils.getPrevWordsInfoFromNthPreviousWord(
179                "abc def", mSpacingAndPunctuations, 2).mPrevWordsInfo[1],
180                WordInfo.BEGINNING_OF_SENTENCE);
181
182        // The following tests reflect the current behavior of the function
183        // RichInputConnection#getNthPreviousWord.
184        // TODO: However at this time, the code does never go
185        // into such a path, so it should be safe to change the behavior of
186        // this function if needed - especially since it does not seem very
187        // logical. These tests are just there to catch any unintentional
188        // changes in the behavior of the RichInputConnection#getPreviousWord method.
189        assertEquals(PrevWordsInfoUtils.getPrevWordsInfoFromNthPreviousWord(
190                "abc def ", mSpacingAndPunctuations, 2).mPrevWordsInfo[0].mWord, "abc");
191        assertEquals(PrevWordsInfoUtils.getPrevWordsInfoFromNthPreviousWord(
192                "abc def.", mSpacingAndPunctuations, 2).mPrevWordsInfo[0].mWord, "abc");
193        assertEquals(PrevWordsInfoUtils.getPrevWordsInfoFromNthPreviousWord(
194                "abc def .", mSpacingAndPunctuations, 2).mPrevWordsInfo[0].mWord, "def");
195        assertEquals(PrevWordsInfoUtils.getPrevWordsInfoFromNthPreviousWord(
196                "abc ", mSpacingAndPunctuations, 2), PrevWordsInfo.BEGINNING_OF_SENTENCE);
197
198        assertEquals(PrevWordsInfoUtils.getPrevWordsInfoFromNthPreviousWord(
199                "abc def", mSpacingAndPunctuations, 1).mPrevWordsInfo[0].mWord, "def");
200        assertEquals(PrevWordsInfoUtils.getPrevWordsInfoFromNthPreviousWord(
201                "abc def ", mSpacingAndPunctuations, 1).mPrevWordsInfo[0].mWord, "def");
202        assertEquals(PrevWordsInfoUtils.getPrevWordsInfoFromNthPreviousWord(
203                "abc 'def", mSpacingAndPunctuations, 1).mPrevWordsInfo[0].mWord, "'def");
204        assertEquals(PrevWordsInfoUtils.getPrevWordsInfoFromNthPreviousWord(
205                "abc def.", mSpacingAndPunctuations, 1), PrevWordsInfo.BEGINNING_OF_SENTENCE);
206        assertEquals(PrevWordsInfoUtils.getPrevWordsInfoFromNthPreviousWord(
207                "abc def .", mSpacingAndPunctuations, 1), PrevWordsInfo.BEGINNING_OF_SENTENCE);
208        assertEquals(PrevWordsInfoUtils.getPrevWordsInfoFromNthPreviousWord(
209                "abc, def", mSpacingAndPunctuations, 2), PrevWordsInfo.EMPTY_PREV_WORDS_INFO);
210        assertEquals(PrevWordsInfoUtils.getPrevWordsInfoFromNthPreviousWord(
211                "abc? def", mSpacingAndPunctuations, 2), PrevWordsInfo.EMPTY_PREV_WORDS_INFO);
212        assertEquals(PrevWordsInfoUtils.getPrevWordsInfoFromNthPreviousWord(
213                "abc! def", mSpacingAndPunctuations, 2), PrevWordsInfo.EMPTY_PREV_WORDS_INFO);
214        assertEquals(PrevWordsInfoUtils.getPrevWordsInfoFromNthPreviousWord(
215                "abc 'def", mSpacingAndPunctuations, 2), PrevWordsInfo.EMPTY_PREV_WORDS_INFO);
216    }
217
218    public void testGetWordRangeAtCursor() {
219        /**
220         * Test logic in getting the word range at the cursor.
221         */
222        final SpacingAndPunctuations SPACE = new SpacingAndPunctuations(
223                mSpacingAndPunctuations, new int[] { Constants.CODE_SPACE });
224        final SpacingAndPunctuations TAB = new SpacingAndPunctuations(
225                mSpacingAndPunctuations, new int[] { Constants.CODE_TAB });
226        final int[] SPACE_TAB = StringUtils.toSortedCodePointArray(" \t");
227        // A character that needs surrogate pair to represent its code point (U+2008A).
228        final String SUPPLEMENTARY_CHAR_STRING = "\uD840\uDC8A";
229        final SpacingAndPunctuations SUPPLEMENTARY_CHAR = new SpacingAndPunctuations(
230                mSpacingAndPunctuations, StringUtils.toSortedCodePointArray(
231                        SUPPLEMENTARY_CHAR_STRING));
232        final String HIRAGANA_WORD = "\u3042\u3044\u3046\u3048\u304A"; // あいうえお
233        final String GREEK_WORD = "\u03BA\u03B1\u03B9"; // και
234
235        ExtractedText et = new ExtractedText();
236        final MockInputMethodService mockInputMethodService = new MockInputMethodService();
237        final RichInputConnection ic = new RichInputConnection(mockInputMethodService);
238        mockInputMethodService.setInputConnection(new MockConnection("word wo", "rd", et));
239        et.startOffset = 0;
240        et.selectionStart = 7;
241        TextRange r;
242
243        ic.beginBatchEdit();
244        // basic case
245        r = ic.getWordRangeAtCursor(SPACE, ScriptUtils.SCRIPT_LATIN);
246        assertTrue(TextUtils.equals("word", r.mWord));
247
248        // tab character instead of space
249        mockInputMethodService.setInputConnection(new MockConnection("one\tword\two", "rd", et));
250        ic.beginBatchEdit();
251        r = ic.getWordRangeAtCursor(TAB, ScriptUtils.SCRIPT_LATIN);
252        ic.endBatchEdit();
253        assertTrue(TextUtils.equals("word", r.mWord));
254
255        // splitting on supplementary character
256        mockInputMethodService.setInputConnection(
257                new MockConnection("one word" + SUPPLEMENTARY_CHAR_STRING + "wo", "rd", et));
258        ic.beginBatchEdit();
259        r = ic.getWordRangeAtCursor(SUPPLEMENTARY_CHAR, ScriptUtils.SCRIPT_LATIN);
260        ic.endBatchEdit();
261        assertTrue(TextUtils.equals("word", r.mWord));
262
263        // split on chars outside the specified script
264        mockInputMethodService.setInputConnection(
265                new MockConnection(HIRAGANA_WORD + "wo", "rd" + GREEK_WORD, et));
266        ic.beginBatchEdit();
267        r = ic.getWordRangeAtCursor(SUPPLEMENTARY_CHAR, ScriptUtils.SCRIPT_LATIN);
268        ic.endBatchEdit();
269        assertTrue(TextUtils.equals("word", r.mWord));
270
271        // likewise for greek
272        mockInputMethodService.setInputConnection(
273                new MockConnection("text" + GREEK_WORD, "text", et));
274        ic.beginBatchEdit();
275        r = ic.getWordRangeAtCursor(SUPPLEMENTARY_CHAR, ScriptUtils.SCRIPT_GREEK);
276        ic.endBatchEdit();
277        assertTrue(TextUtils.equals(GREEK_WORD, r.mWord));
278    }
279
280    /**
281     * Test logic in getting the word range at the cursor.
282     */
283    public void testGetSuggestionSpansAtWord() {
284        helpTestGetSuggestionSpansAtWord(10);
285        helpTestGetSuggestionSpansAtWord(12);
286        helpTestGetSuggestionSpansAtWord(15);
287        helpTestGetSuggestionSpansAtWord(16);
288    }
289
290    private void helpTestGetSuggestionSpansAtWord(final int cursorPos) {
291        final SpacingAndPunctuations SPACE = new SpacingAndPunctuations(
292                mSpacingAndPunctuations, new int[] { Constants.CODE_SPACE });
293        final MockInputMethodService mockInputMethodService = new MockInputMethodService();
294        final RichInputConnection ic = new RichInputConnection(mockInputMethodService);
295
296        final String[] SUGGESTIONS1 = { "swing", "strong" };
297        final String[] SUGGESTIONS2 = { "storing", "strung" };
298
299        // Test the usual case.
300        SpannableString text = new SpannableString("This is a string for test");
301        text.setSpan(new SuggestionSpan(Locale.ENGLISH, SUGGESTIONS1, 0 /* flags */),
302                10 /* start */, 16 /* end */, 0 /* flags */);
303        mockInputMethodService.setInputConnection(new MockConnection(text, cursorPos));
304        TextRange r;
305        SuggestionSpan[] suggestions;
306
307        r = ic.getWordRangeAtCursor(SPACE, ScriptUtils.SCRIPT_LATIN);
308        suggestions = r.getSuggestionSpansAtWord();
309        assertEquals(suggestions.length, 1);
310        MoreAsserts.assertEquals(suggestions[0].getSuggestions(), SUGGESTIONS1);
311
312        // Test the case with 2 suggestion spans in the same place.
313        text = new SpannableString("This is a string for test");
314        text.setSpan(new SuggestionSpan(Locale.ENGLISH, SUGGESTIONS1, 0 /* flags */),
315                10 /* start */, 16 /* end */, 0 /* flags */);
316        text.setSpan(new SuggestionSpan(Locale.ENGLISH, SUGGESTIONS2, 0 /* flags */),
317                10 /* start */, 16 /* end */, 0 /* flags */);
318        mockInputMethodService.setInputConnection(new MockConnection(text, cursorPos));
319        r = ic.getWordRangeAtCursor(SPACE, ScriptUtils.SCRIPT_LATIN);
320        suggestions = r.getSuggestionSpansAtWord();
321        assertEquals(suggestions.length, 2);
322        MoreAsserts.assertEquals(suggestions[0].getSuggestions(), SUGGESTIONS1);
323        MoreAsserts.assertEquals(suggestions[1].getSuggestions(), SUGGESTIONS2);
324
325        // Test a case with overlapping spans, 2nd extending past the start of the word
326        text = new SpannableString("This is a string for test");
327        text.setSpan(new SuggestionSpan(Locale.ENGLISH, SUGGESTIONS1, 0 /* flags */),
328                10 /* start */, 16 /* end */, 0 /* flags */);
329        text.setSpan(new SuggestionSpan(Locale.ENGLISH, SUGGESTIONS2, 0 /* flags */),
330                5 /* start */, 16 /* end */, 0 /* flags */);
331        mockInputMethodService.setInputConnection(new MockConnection(text, cursorPos));
332        r = ic.getWordRangeAtCursor(SPACE, ScriptUtils.SCRIPT_LATIN);
333        suggestions = r.getSuggestionSpansAtWord();
334        assertEquals(suggestions.length, 1);
335        MoreAsserts.assertEquals(suggestions[0].getSuggestions(), SUGGESTIONS1);
336
337        // Test a case with overlapping spans, 2nd extending past the end of the word
338        text = new SpannableString("This is a string for test");
339        text.setSpan(new SuggestionSpan(Locale.ENGLISH, SUGGESTIONS1, 0 /* flags */),
340                10 /* start */, 16 /* end */, 0 /* flags */);
341        text.setSpan(new SuggestionSpan(Locale.ENGLISH, SUGGESTIONS2, 0 /* flags */),
342                10 /* start */, 20 /* end */, 0 /* flags */);
343        mockInputMethodService.setInputConnection(new MockConnection(text, cursorPos));
344        r = ic.getWordRangeAtCursor(SPACE, ScriptUtils.SCRIPT_LATIN);
345        suggestions = r.getSuggestionSpansAtWord();
346        assertEquals(suggestions.length, 1);
347        MoreAsserts.assertEquals(suggestions[0].getSuggestions(), SUGGESTIONS1);
348
349        // Test a case with overlapping spans, 2nd extending past both ends of the word
350        text = new SpannableString("This is a string for test");
351        text.setSpan(new SuggestionSpan(Locale.ENGLISH, SUGGESTIONS1, 0 /* flags */),
352                10 /* start */, 16 /* end */, 0 /* flags */);
353        text.setSpan(new SuggestionSpan(Locale.ENGLISH, SUGGESTIONS2, 0 /* flags */),
354                5 /* start */, 20 /* end */, 0 /* flags */);
355        mockInputMethodService.setInputConnection(new MockConnection(text, cursorPos));
356        r = ic.getWordRangeAtCursor(SPACE, ScriptUtils.SCRIPT_LATIN);
357        suggestions = r.getSuggestionSpansAtWord();
358        assertEquals(suggestions.length, 1);
359        MoreAsserts.assertEquals(suggestions[0].getSuggestions(), SUGGESTIONS1);
360
361        // Test a case with overlapping spans, none right on the word
362        text = new SpannableString("This is a string for test");
363        text.setSpan(new SuggestionSpan(Locale.ENGLISH, SUGGESTIONS1, 0 /* flags */),
364                5 /* start */, 16 /* end */, 0 /* flags */);
365        text.setSpan(new SuggestionSpan(Locale.ENGLISH, SUGGESTIONS2, 0 /* flags */),
366                5 /* start */, 20 /* end */, 0 /* flags */);
367        mockInputMethodService.setInputConnection(new MockConnection(text, cursorPos));
368        r = ic.getWordRangeAtCursor(SPACE, ScriptUtils.SCRIPT_LATIN);
369        suggestions = r.getSuggestionSpansAtWord();
370        assertEquals(suggestions.length, 0);
371    }
372
373    public void testCursorTouchingWord() {
374        final MockInputMethodService ims = new MockInputMethodService();
375        final RichInputConnection ic = new RichInputConnection(ims);
376        final SpacingAndPunctuations sap = mSpacingAndPunctuations;
377
378        ims.setInputConnection(new MockConnection("users", 5));
379        ic.resetCachesUponCursorMoveAndReturnSuccess(ims.cursorPos(), ims.cursorPos(), true);
380        assertTrue(ic.isCursorTouchingWord(sap));
381
382        ims.setInputConnection(new MockConnection("users'", 5));
383        ic.resetCachesUponCursorMoveAndReturnSuccess(ims.cursorPos(), ims.cursorPos(), true);
384        assertTrue(ic.isCursorTouchingWord(sap));
385
386        ims.setInputConnection(new MockConnection("users'", 6));
387        ic.resetCachesUponCursorMoveAndReturnSuccess(ims.cursorPos(), ims.cursorPos(), true);
388        assertTrue(ic.isCursorTouchingWord(sap));
389
390        ims.setInputConnection(new MockConnection("'users'", 6));
391        ic.resetCachesUponCursorMoveAndReturnSuccess(ims.cursorPos(), ims.cursorPos(), true);
392        assertTrue(ic.isCursorTouchingWord(sap));
393
394        ims.setInputConnection(new MockConnection("'users'", 7));
395        ic.resetCachesUponCursorMoveAndReturnSuccess(ims.cursorPos(), ims.cursorPos(), true);
396        assertTrue(ic.isCursorTouchingWord(sap));
397
398        ims.setInputConnection(new MockConnection("users '", 6));
399        ic.resetCachesUponCursorMoveAndReturnSuccess(ims.cursorPos(), ims.cursorPos(), true);
400        assertFalse(ic.isCursorTouchingWord(sap));
401
402        ims.setInputConnection(new MockConnection("users '", 7));
403        ic.resetCachesUponCursorMoveAndReturnSuccess(ims.cursorPos(), ims.cursorPos(), true);
404        assertFalse(ic.isCursorTouchingWord(sap));
405
406        ims.setInputConnection(new MockConnection("re-", 3));
407        ic.resetCachesUponCursorMoveAndReturnSuccess(ims.cursorPos(), ims.cursorPos(), true);
408        assertTrue(ic.isCursorTouchingWord(sap));
409
410        ims.setInputConnection(new MockConnection("re--", 4));
411        ic.resetCachesUponCursorMoveAndReturnSuccess(ims.cursorPos(), ims.cursorPos(), true);
412        assertFalse(ic.isCursorTouchingWord(sap));
413
414        ims.setInputConnection(new MockConnection("-", 1));
415        ic.resetCachesUponCursorMoveAndReturnSuccess(ims.cursorPos(), ims.cursorPos(), true);
416        assertFalse(ic.isCursorTouchingWord(sap));
417
418        ims.setInputConnection(new MockConnection("--", 2));
419        ic.resetCachesUponCursorMoveAndReturnSuccess(ims.cursorPos(), ims.cursorPos(), true);
420        assertFalse(ic.isCursorTouchingWord(sap));
421
422        ims.setInputConnection(new MockConnection(" -", 2));
423        ic.resetCachesUponCursorMoveAndReturnSuccess(ims.cursorPos(), ims.cursorPos(), true);
424        assertFalse(ic.isCursorTouchingWord(sap));
425
426        ims.setInputConnection(new MockConnection(" --", 3));
427        ic.resetCachesUponCursorMoveAndReturnSuccess(ims.cursorPos(), ims.cursorPos(), true);
428        assertFalse(ic.isCursorTouchingWord(sap));
429
430        ims.setInputConnection(new MockConnection(" users '", 1));
431        ic.resetCachesUponCursorMoveAndReturnSuccess(ims.cursorPos(), ims.cursorPos(), true);
432        assertTrue(ic.isCursorTouchingWord(sap));
433
434        ims.setInputConnection(new MockConnection(" users '", 3));
435        ic.resetCachesUponCursorMoveAndReturnSuccess(ims.cursorPos(), ims.cursorPos(), true);
436        assertTrue(ic.isCursorTouchingWord(sap));
437
438        ims.setInputConnection(new MockConnection(" users '", 7));
439        ic.resetCachesUponCursorMoveAndReturnSuccess(ims.cursorPos(), ims.cursorPos(), true);
440        assertFalse(ic.isCursorTouchingWord(sap));
441
442        ims.setInputConnection(new MockConnection(" users are", 7));
443        ic.resetCachesUponCursorMoveAndReturnSuccess(ims.cursorPos(), ims.cursorPos(), true);
444        assertTrue(ic.isCursorTouchingWord(sap));
445
446        ims.setInputConnection(new MockConnection(" users 'are", 7));
447        ic.resetCachesUponCursorMoveAndReturnSuccess(ims.cursorPos(), ims.cursorPos(), true);
448        assertFalse(ic.isCursorTouchingWord(sap));
449    }
450}
451