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