ImeTest.java revision 03b57e008b61dfcb1fbad3aea950ae0e001748b0
1// Copyright 2013 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5package org.chromium.content.browser.input;
6
7import android.app.Activity;
8import android.content.ClipData;
9import android.content.ClipboardManager;
10import android.content.Context;
11import android.test.suitebuilder.annotation.MediumTest;
12import android.test.suitebuilder.annotation.SmallTest;
13import android.text.Editable;
14import android.text.Selection;
15import android.text.TextUtils;
16import android.view.KeyEvent;
17import android.view.View;
18import android.view.inputmethod.EditorInfo;
19
20import org.chromium.base.ThreadUtils;
21import org.chromium.base.test.util.Feature;
22import org.chromium.base.test.util.UrlUtils;
23import org.chromium.content.browser.ContentViewCore;
24import org.chromium.content.browser.test.util.Criteria;
25import org.chromium.content.browser.test.util.CriteriaHelper;
26import org.chromium.content.browser.test.util.DOMUtils;
27import org.chromium.content.browser.test.util.TestCallbackHelperContainer;
28import org.chromium.content.browser.test.util.TestInputMethodManagerWrapper;
29import org.chromium.content_shell_apk.ContentShellTestBase;
30
31import java.util.ArrayList;
32import java.util.concurrent.Callable;
33
34/**
35 * Integration tests for text input using cases based on fixed regressions.
36 */
37public class ImeTest extends ContentShellTestBase {
38
39    private static final String DATA_URL = UrlUtils.encodeHtmlDataUri(
40            "<html><head><meta name=\"viewport\"" +
41            "content=\"width=device-width, initial-scale=2.0, maximum-scale=2.0\" /></head>" +
42            "<body><form action=\"about:blank\">" +
43            "<input id=\"input_text\" type=\"text\" /><br/>" +
44            "<input id=\"input_radio\" type=\"radio\" style=\"width:50px;height:50px\" />" +
45            "<br/><textarea id=\"textarea\" rows=\"4\" cols=\"20\"></textarea>" +
46            "</form></body></html>");
47
48    private TestAdapterInputConnection mConnection;
49    private ImeAdapter mImeAdapter;
50
51    private ContentViewCore mContentViewCore;
52    private TestCallbackHelperContainer mCallbackContainer;
53    private TestInputMethodManagerWrapper mInputMethodManagerWrapper;
54
55    @Override
56    public void setUp() throws Exception {
57        super.setUp();
58
59        launchContentShellWithUrl(DATA_URL);
60        assertTrue("Page failed to load", waitForActiveShellToBeDoneLoading());
61        mContentViewCore = getContentViewCore();
62
63        mInputMethodManagerWrapper = new TestInputMethodManagerWrapper(mContentViewCore);
64        getImeAdapter().setInputMethodManagerWrapper(mInputMethodManagerWrapper);
65        assertEquals(0, mInputMethodManagerWrapper.getShowSoftInputCounter());
66        mContentViewCore.setAdapterInputConnectionFactory(
67                new TestAdapterInputConnectionFactory());
68
69        mCallbackContainer = new TestCallbackHelperContainer(mContentViewCore);
70        // TODO(aurimas) remove this wait once crbug.com/179511 is fixed.
71        assertWaitForPageScaleFactorMatch(2);
72        assertTrue(DOMUtils.waitForNonZeroNodeBounds(
73                mContentViewCore, "input_text"));
74        DOMUtils.clickNode(this, mContentViewCore, "input_text");
75        assertWaitForKeyboardStatus(true);
76
77        mConnection = (TestAdapterInputConnection) getAdapterInputConnection();
78        mImeAdapter = getImeAdapter();
79
80        waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 0, "", 0, 0, -1, -1);
81        assertEquals(1, mInputMethodManagerWrapper.getShowSoftInputCounter());
82        assertEquals(0, mInputMethodManagerWrapper.getEditorInfo().initialSelStart);
83        assertEquals(0, mInputMethodManagerWrapper.getEditorInfo().initialSelEnd);
84    }
85
86    @MediumTest
87    @Feature({"TextInput", "Main"})
88    public void testKeyboardDismissedAfterClickingGo() throws Throwable {
89        setComposingText(mConnection, "hello", 1);
90        waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 1, "hello", 5, 5, 0, 5);
91
92        performGo(getAdapterInputConnection(), mCallbackContainer);
93
94        waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 2, "", 0, 0, -1, -1);
95        assertWaitForKeyboardStatus(false);
96    }
97
98    @SmallTest
99    @Feature({"TextInput", "Main"})
100    @RerunWithUpdatedContainerView
101    public void testGetTextUpdatesAfterEnteringText() throws Throwable {
102        setComposingText(mConnection, "h", 1);
103        waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 1, "h", 1, 1, 0, 1);
104        assertEquals(1, mInputMethodManagerWrapper.getShowSoftInputCounter());
105
106        setComposingText(mConnection, "he", 1);
107        waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 2, "he", 2, 2, 0, 2);
108        assertEquals(1, mInputMethodManagerWrapper.getShowSoftInputCounter());
109
110        setComposingText(mConnection, "hel", 1);
111        waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 3, "hel", 3, 3, 0, 3);
112        assertEquals(1, mInputMethodManagerWrapper.getShowSoftInputCounter());
113
114        commitText(mConnection, "hel", 1);
115        waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 4, "hel", 3, 3, -1, -1);
116        assertEquals(1, mInputMethodManagerWrapper.getShowSoftInputCounter());
117    }
118
119    @SmallTest
120    @Feature({"TextInput"})
121    @RerunWithUpdatedContainerView
122    public void testImeCopy() throws Exception {
123        commitText(mConnection, "hello", 1);
124        waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 1, "hello", 5, 5, -1, -1);
125
126        setSelection(mConnection, 2, 5);
127        waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 2, "hello", 2, 5, -1, -1);
128
129        copy(mImeAdapter);
130        assertClipboardContents(getActivity(), "llo");
131    }
132
133    @SmallTest
134    @Feature({"TextInput"})
135    public void testEnterTextAndRefocus() throws Exception {
136        commitText(mConnection, "hello", 1);
137        waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 1, "hello", 5, 5, -1, -1);
138
139        DOMUtils.clickNode(this, mContentViewCore, "input_radio");
140        assertWaitForKeyboardStatus(false);
141
142        DOMUtils.clickNode(this, mContentViewCore, "input_text");
143        assertWaitForKeyboardStatus(true);
144        assertEquals(5, mInputMethodManagerWrapper.getEditorInfo().initialSelStart);
145        assertEquals(5, mInputMethodManagerWrapper.getEditorInfo().initialSelEnd);
146    }
147
148    @SmallTest
149    @Feature({"TextInput"})
150    public void testKeyboardNotDismissedAfterCopySelection() throws Exception {
151        commitText(mConnection, "Sample Text", 1);
152        waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 1,
153                "Sample Text", 11, 11, -1, -1);
154        DOMUtils.clickNode(this, mContentViewCore, "input_text");
155        assertWaitForKeyboardStatus(true);
156        DOMUtils.longPressNode(this, mContentViewCore, "input_text");
157        selectAll(mImeAdapter);
158        copy(mImeAdapter);
159        assertWaitForKeyboardStatus(true);
160        assertEquals(11, Selection.getSelectionEnd(mContentViewCore.getEditableForTest()));
161        assertEquals(11, Selection.getSelectionEnd(mContentViewCore.getEditableForTest()));
162    }
163
164    @SmallTest
165    @Feature({"TextInput"})
166    public void testImeNotShownOnLongPressingEmptyInput() throws Exception {
167        DOMUtils.focusNode(mContentViewCore, "input_radio");
168        DOMUtils.longPressNode(this, mContentViewCore, "input_text");
169        assertWaitForKeyboardStatus(false);
170        commitText(mConnection, "Sample Text", 1);
171        DOMUtils.longPressNode(this, mContentViewCore, "input_text");
172        assertWaitForKeyboardStatus(true);
173    }
174
175    @SmallTest
176    @Feature({"TextInput"})
177    public void testImeCut() throws Exception {
178        commitText(mConnection, "snarful", 1);
179        waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 1, "snarful", 7, 7, -1, -1);
180
181        setSelection(mConnection, 1, 5);
182        waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 2, "snarful", 1, 5, -1, -1);
183
184        cut(mImeAdapter);
185        waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 3, "sul", 1, 1, -1, -1);
186
187        assertClipboardContents(getActivity(), "narf");
188    }
189
190    @SmallTest
191    @Feature({"TextInput"})
192    public void testImePaste() throws Exception {
193        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
194            @Override
195            public void run() {
196                ClipboardManager clipboardManager =
197                        (ClipboardManager) getActivity().getSystemService(
198                                Context.CLIPBOARD_SERVICE);
199                clipboardManager.setPrimaryClip(ClipData.newPlainText("blarg", "blarg"));
200            }
201        });
202
203        paste(mImeAdapter);
204        waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 1, "blarg", 5, 5, -1, -1);
205
206        setSelection(mConnection, 3, 5);
207        waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 2, "blarg", 3, 5, -1, -1);
208
209        paste(mImeAdapter);
210        // Paste is a two step process when there is a non-zero selection.
211        waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 3, "bla", 3, 3, -1, -1);
212        waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 4, "blablarg", 8, 8, -1, -1);
213
214        paste(mImeAdapter);
215        waitAndVerifyEditableCallback(
216                mConnection.mImeUpdateQueue, 5, "blablargblarg", 13, 13, -1, -1);
217    }
218
219    @SmallTest
220    @Feature({"TextInput"})
221    public void testImeSelectAndUnSelectAll() throws Exception {
222        commitText(mConnection, "hello", 1);
223        waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 1, "hello", 5, 5, -1, -1);
224
225        selectAll(mImeAdapter);
226        waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 2, "hello", 0, 5, -1, -1);
227
228        unselect(mImeAdapter);
229        waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 3, "", 0, 0, -1, -1);
230
231        assertWaitForKeyboardStatus(false);
232    }
233
234    @SmallTest
235    @Feature({"TextInput", "Main"})
236    public void testShowImeIfNeeded() throws Throwable {
237        // showImeIfNeeded() is now implicitly called by the updated focus
238        // heuristic so no need to call explicitly. http://crbug.com/371927
239        DOMUtils.focusNode(mContentViewCore, "input_radio");
240        assertWaitForKeyboardStatus(false);
241
242        DOMUtils.focusNode(mContentViewCore, "input_text");
243        assertWaitForKeyboardStatus(true);
244    }
245
246    @SmallTest
247    @Feature({"TextInput", "Main"})
248    public void testFinishComposingText() throws Throwable {
249        DOMUtils.focusNode(mContentViewCore, "input_radio");
250        assertWaitForKeyboardStatus(false);
251        DOMUtils.focusNode(mContentViewCore, "textarea");
252        assertWaitForKeyboardStatus(true);
253
254        mConnection = (TestAdapterInputConnection) getAdapterInputConnection();
255        waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 0, "", 0, 0, -1, -1);
256
257        commitText(mConnection, "hllo", 1);
258        waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 1, "hllo", 4, 4, -1, -1);
259
260        commitText(mConnection, " ", 1);
261        waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 2, "hllo ", 5, 5, -1, -1);
262
263        setSelection(mConnection, 1, 1);
264        waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 3, "hllo ", 1, 1, -1, -1);
265
266        setComposingRegion(mConnection, 0, 4);
267        waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 4, "hllo ", 1, 1, 0, 4);
268
269        finishComposingText(mConnection);
270        waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 5, "hllo ", 1, 1, -1, -1);
271
272        commitText(mConnection, "\n", 1);
273        waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 6, "h\nllo ", 2, 2, -1, -1);
274    }
275
276    private int getTypedKeycodeGuess(String before, String after) {
277        KeyEvent ev = ImeAdapter.getTypedKeyEventGuess(before, after);
278        if (ev == null) return -1;
279        return ev.getKeyCode();
280    }
281
282    @SmallTest
283    @Feature({"TextInput", "Main"})
284    public void testGuessedKeyCodeFromTyping() throws Throwable {
285        assertEquals(-1, getTypedKeycodeGuess(null, ""));
286        assertEquals(KeyEvent.KEYCODE_X, getTypedKeycodeGuess(null, "x"));
287        assertEquals(-1, getTypedKeycodeGuess(null, "xyz"));
288
289        assertEquals(-1, getTypedKeycodeGuess("abc", "abc"));
290        assertEquals(KeyEvent.KEYCODE_DEL, getTypedKeycodeGuess("abc", ""));
291
292        assertEquals(KeyEvent.KEYCODE_H, getTypedKeycodeGuess("", "h"));
293        assertEquals(KeyEvent.KEYCODE_DEL, getTypedKeycodeGuess("h", ""));
294        assertEquals(KeyEvent.KEYCODE_E, getTypedKeycodeGuess("h", "he"));
295        assertEquals(KeyEvent.KEYCODE_L, getTypedKeycodeGuess("he", "hel"));
296        assertEquals(KeyEvent.KEYCODE_O, getTypedKeycodeGuess("hel", "helo"));
297        assertEquals(KeyEvent.KEYCODE_DEL, getTypedKeycodeGuess("helo", "hel"));
298        assertEquals(KeyEvent.KEYCODE_L, getTypedKeycodeGuess("hel", "hell"));
299        assertEquals(KeyEvent.KEYCODE_L, getTypedKeycodeGuess("hell", "helll"));
300        assertEquals(KeyEvent.KEYCODE_DEL, getTypedKeycodeGuess("helll", "hell"));
301        assertEquals(KeyEvent.KEYCODE_O, getTypedKeycodeGuess("hell", "hello"));
302
303        assertEquals(KeyEvent.KEYCODE_X, getTypedKeycodeGuess("xxx", "xxxx"));
304        assertEquals(KeyEvent.KEYCODE_X, getTypedKeycodeGuess("xxx", "xxxxx"));
305        assertEquals(KeyEvent.KEYCODE_DEL, getTypedKeycodeGuess("xxx", "xx"));
306        assertEquals(KeyEvent.KEYCODE_DEL, getTypedKeycodeGuess("xxx", "x"));
307
308        assertEquals(KeyEvent.KEYCODE_Y, getTypedKeycodeGuess("xxx", "xxxy"));
309        assertEquals(KeyEvent.KEYCODE_Y, getTypedKeycodeGuess("xxx", "xxxxy"));
310        assertEquals(-1, getTypedKeycodeGuess("xxx", "xy"));
311        assertEquals(-1, getTypedKeycodeGuess("xxx", "y"));
312
313        assertEquals(-1, getTypedKeycodeGuess("foo", "bar"));
314        assertEquals(-1, getTypedKeycodeGuess("foo", "bars"));
315        assertEquals(-1, getTypedKeycodeGuess("foo", "ba"));
316
317        // Some characters also require modifiers so we have to check the full event.
318        KeyEvent ev = ImeAdapter.getTypedKeyEventGuess(null, "!");
319        assertEquals(KeyEvent.KEYCODE_1, ev.getKeyCode());
320        assertTrue(ev.isShiftPressed());
321    }
322
323    @SmallTest
324    @Feature({"TextInput", "Main"})
325    public void testKeyCodesWhileComposingText() throws Throwable {
326        DOMUtils.focusNode(mContentViewCore, "textarea");
327        assertWaitForKeyboardStatus(true);
328
329        // The calls below are a reflection of what the stock Google Keyboard (Android 4.4) sends
330        // when the noted key is touched on screen.  Exercise care when altering to make sure
331        // that the test reflects reality.  If this test breaks, it's possible that code has
332        // changed and different calls need to be made instead.
333        mConnection = (TestAdapterInputConnection) getAdapterInputConnection();
334        waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 0, "", 0, 0, -1, -1);
335
336        // H
337        expectUpdateStateCall(mConnection);
338        setComposingText(mConnection, "h", 1);
339        assertEquals(KeyEvent.KEYCODE_H, mImeAdapter.mLastSyntheticKeyCode);
340        assertUpdateStateCall(mConnection, 1000);
341        assertEquals("h", mConnection.getTextBeforeCursor(9, 0));
342
343        // O
344        expectUpdateStateCall(mConnection);
345        setComposingText(mConnection, "ho", 1);
346        assertEquals(KeyEvent.KEYCODE_O, mImeAdapter.mLastSyntheticKeyCode);
347        assertUpdateStateCall(mConnection, 1000);
348        assertEquals("ho", mConnection.getTextBeforeCursor(9, 0));
349
350        // DEL
351        expectUpdateStateCall(mConnection);
352        setComposingText(mConnection, "h", 1);
353        assertEquals(KeyEvent.KEYCODE_DEL, mImeAdapter.mLastSyntheticKeyCode);
354        assertUpdateStateCall(mConnection, 1000);
355        setComposingRegion(mConnection, 0, 1);  // DEL calls cancelComposition() then restarts
356        setComposingText(mConnection, "h", 1);
357        assertEquals("h", mConnection.getTextBeforeCursor(9, 0));
358
359        // I
360        setComposingText(mConnection, "hi", 1);
361        assertEquals(KeyEvent.KEYCODE_I, mImeAdapter.mLastSyntheticKeyCode);
362        assertEquals("hi", mConnection.getTextBeforeCursor(9, 0));
363
364        // SPACE
365        commitText(mConnection, "hi", 1);
366        assertEquals(-1, mImeAdapter.mLastSyntheticKeyCode);
367        commitText(mConnection, " ", 1);
368        assertEquals(KeyEvent.KEYCODE_SPACE, mImeAdapter.mLastSyntheticKeyCode);
369        assertEquals("hi ", mConnection.getTextBeforeCursor(9, 0));
370
371        // DEL
372        deleteSurroundingText(mConnection, 1, 0);
373        assertEquals(KeyEvent.KEYCODE_DEL, mImeAdapter.mLastSyntheticKeyCode);
374        setComposingRegion(mConnection, 0, 2);
375        assertEquals("hi", mConnection.getTextBeforeCursor(9, 0));
376
377        // DEL
378        setComposingText(mConnection, "h", 1);
379        assertEquals(KeyEvent.KEYCODE_DEL, mImeAdapter.mLastSyntheticKeyCode);
380        assertEquals("h", mConnection.getTextBeforeCursor(9, 0));
381
382        // DEL
383        commitText(mConnection, "", 1);
384        assertEquals(KeyEvent.KEYCODE_DEL, mImeAdapter.mLastSyntheticKeyCode);
385        assertEquals("", mConnection.getTextBeforeCursor(9, 0));
386
387        // DEL (on empty input)
388        deleteSurroundingText(mConnection, 1, 0);  // DEL on empty still sends 1,0
389        assertEquals(KeyEvent.KEYCODE_DEL, mImeAdapter.mLastSyntheticKeyCode);
390        assertEquals("", mConnection.getTextBeforeCursor(9, 0));
391    }
392
393    @SmallTest
394    @Feature({"TextInput", "Main"})
395    public void testKeyCodesWhileTypingText() throws Throwable {
396        DOMUtils.focusNode(mContentViewCore, "textarea");
397        assertWaitForKeyboardStatus(true);
398
399        // The calls below are a reflection of what the Hacker's Keyboard sends  when the noted
400        // key is touched on screen.  Exercise care when altering to make sure that the test
401        // reflects reality.
402        mConnection = (TestAdapterInputConnection) getAdapterInputConnection();
403        waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 0, "", 0, 0, -1, -1);
404
405        // H
406        expectUpdateStateCall(mConnection);
407        commitText(mConnection, "h", 1);
408        assertEquals(KeyEvent.KEYCODE_H, mImeAdapter.mLastSyntheticKeyCode);
409        assertEquals("h", mConnection.getTextBeforeCursor(9, 0));
410        assertUpdateStateCall(mConnection, 1000);
411        assertEquals("h", mConnection.getTextBeforeCursor(9, 0));
412
413        // O
414        expectUpdateStateCall(mConnection);
415        commitText(mConnection, "o", 1);
416        assertEquals(KeyEvent.KEYCODE_O, mImeAdapter.mLastSyntheticKeyCode);
417        assertEquals("ho", mConnection.getTextBeforeCursor(9, 0));
418        assertUpdateStateCall(mConnection, 1000);
419        assertEquals("ho", mConnection.getTextBeforeCursor(9, 0));
420
421        // DEL
422        expectUpdateStateCall(mConnection);
423        deleteSurroundingText(mConnection, 1, 0);
424        assertEquals(KeyEvent.KEYCODE_DEL, mImeAdapter.mLastSyntheticKeyCode);
425        assertEquals("h", mConnection.getTextBeforeCursor(9, 0));
426        assertUpdateStateCall(mConnection, 1000);
427        assertEquals("h", mConnection.getTextBeforeCursor(9, 0));
428
429        // I
430        expectUpdateStateCall(mConnection);
431        commitText(mConnection, "i", 1);
432        assertEquals(KeyEvent.KEYCODE_I, mImeAdapter.mLastSyntheticKeyCode);
433        assertEquals("hi", mConnection.getTextBeforeCursor(9, 0));
434        assertUpdateStateCall(mConnection, 1000);
435        assertEquals("hi", mConnection.getTextBeforeCursor(9, 0));
436
437        // SPACE
438        expectUpdateStateCall(mConnection);
439        commitText(mConnection, " ", 1);
440        assertEquals(KeyEvent.KEYCODE_SPACE, mImeAdapter.mLastSyntheticKeyCode);
441        assertEquals("hi ", mConnection.getTextBeforeCursor(9, 0));
442        assertUpdateStateCall(mConnection, 1000);
443        assertEquals("hi ", mConnection.getTextBeforeCursor(9, 0));
444
445        // DEL
446        expectUpdateStateCall(mConnection);
447        deleteSurroundingText(mConnection, 1, 0);
448        assertEquals(KeyEvent.KEYCODE_DEL, mImeAdapter.mLastSyntheticKeyCode);
449        assertEquals("hi", mConnection.getTextBeforeCursor(9, 0));
450        assertUpdateStateCall(mConnection, 1000);
451        assertEquals("hi", mConnection.getTextBeforeCursor(9, 0));
452
453        // DEL
454        expectUpdateStateCall(mConnection);
455        deleteSurroundingText(mConnection, 1, 0);
456        assertEquals(KeyEvent.KEYCODE_DEL, mImeAdapter.mLastSyntheticKeyCode);
457        assertEquals("h", mConnection.getTextBeforeCursor(9, 0));
458        assertUpdateStateCall(mConnection, 1000);
459        assertEquals("h", mConnection.getTextBeforeCursor(9, 0));
460
461        // DEL
462        expectUpdateStateCall(mConnection);
463        deleteSurroundingText(mConnection, 1, 0);
464        assertEquals(KeyEvent.KEYCODE_DEL, mImeAdapter.mLastSyntheticKeyCode);
465        assertEquals("", mConnection.getTextBeforeCursor(9, 0));
466        assertUpdateStateCall(mConnection, 1000);
467        assertEquals("", mConnection.getTextBeforeCursor(9, 0));
468
469        // DEL (on empty input)
470        deleteSurroundingText(mConnection, 1, 0);  // DEL on empty still sends 1,0
471        assertEquals(KeyEvent.KEYCODE_DEL, mImeAdapter.mLastSyntheticKeyCode);
472        assertEquals("", mConnection.getTextBeforeCursor(9, 0));
473    }
474
475    @SmallTest
476    @Feature({"TextInput", "Main"})
477    public void testSetComposingRegionOutOfBounds() throws Throwable {
478        DOMUtils.focusNode(mContentViewCore, "textarea");
479        assertWaitForKeyboardStatus(true);
480
481        mConnection = (TestAdapterInputConnection) getAdapterInputConnection();
482        waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 0, "", 0, 0, -1, -1);
483        setComposingText(mConnection, "hello", 1);
484
485        setComposingRegion(mConnection, 0, 0);
486        setComposingRegion(mConnection, 0, 9);
487        setComposingRegion(mConnection, 9, 0);
488    }
489
490    @SmallTest
491    @Feature({"TextInput", "Main"})
492    public void testEnterKeyEventWhileComposingText() throws Throwable {
493        DOMUtils.focusNode(mContentViewCore, "input_radio");
494        assertWaitForKeyboardStatus(false);
495        DOMUtils.focusNode(mContentViewCore, "textarea");
496        assertWaitForKeyboardStatus(true);
497
498        mConnection = (TestAdapterInputConnection) getAdapterInputConnection();
499        waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 0, "", 0, 0, -1, -1);
500
501        setComposingText(mConnection, "hello", 1);
502        waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 1, "hello", 5, 5, 0, 5);
503
504        getInstrumentation().runOnMainSync(new Runnable() {
505            @Override
506            public void run() {
507                mConnection.sendKeyEvent(
508                        new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER));
509                mConnection.sendKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER));
510            }
511        });
512
513        // TODO(aurimas): remove this workaround when crbug.com/278584 is fixed.
514        waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 2, "hello", 5, 5, -1, -1);
515        // The second new line is not a user visible/editable one, it is a side-effect of Blink
516        // using <br> internally.
517        waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 3, "hello\n\n", 6, 6, -1, -1);
518    }
519
520    @SmallTest
521    @Feature({"TextInput", "Main"})
522    public void testTransitionsWhileComposingText() throws Throwable {
523        DOMUtils.focusNode(mContentViewCore, "textarea");
524        assertWaitForKeyboardStatus(true);
525
526        mConnection = (TestAdapterInputConnection) getAdapterInputConnection();
527        waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 0, "", 0, 0, -1, -1);
528
529        // H
530        expectUpdateStateCall(mConnection);
531        setComposingText(mConnection, "h", 1);
532        assertEquals(KeyEvent.KEYCODE_H, mImeAdapter.mLastSyntheticKeyCode);
533
534        // Simulate switch of input fields.
535        finishComposingText(mConnection);
536
537        // H
538        expectUpdateStateCall(mConnection);
539        setComposingText(mConnection, "h", 1);
540        assertEquals(KeyEvent.KEYCODE_H, mImeAdapter.mLastSyntheticKeyCode);
541    }
542
543    private void performGo(final AdapterInputConnection inputConnection,
544            TestCallbackHelperContainer testCallbackHelperContainer) throws Throwable {
545        handleBlockingCallbackAction(
546                testCallbackHelperContainer.getOnPageFinishedHelper(),
547                new Runnable() {
548                    @Override
549                    public void run() {
550                        inputConnection.performEditorAction(EditorInfo.IME_ACTION_GO);
551                    }
552                });
553    }
554
555    private void assertWaitForKeyboardStatus(final boolean show) throws InterruptedException {
556        assertTrue(CriteriaHelper.pollForCriteria(new Criteria() {
557            @Override
558            public boolean isSatisfied() {
559                return show == getImeAdapter().mIsShowWithoutHideOutstanding &&
560                        (!show || getAdapterInputConnection() != null);
561            }
562        }));
563    }
564
565    private void waitAndVerifyEditableCallback(final ArrayList<TestImeState> states,
566            final int index, String text, int selectionStart, int selectionEnd,
567            int compositionStart, int compositionEnd) throws InterruptedException {
568        assertTrue(CriteriaHelper.pollForCriteria(new Criteria() {
569            @Override
570            public boolean isSatisfied() {
571                return states.size() > index;
572            }
573        }));
574        states.get(index).assertEqualState(
575                text, selectionStart, selectionEnd, compositionStart, compositionEnd);
576    }
577
578    private void expectUpdateStateCall(final TestAdapterInputConnection connection) {
579        connection.mImeUpdateQueue.clear();
580    }
581
582    private void assertUpdateStateCall(final TestAdapterInputConnection connection, int maxms)
583            throws Exception {
584        while (connection.mImeUpdateQueue.size() == 0 && maxms > 0) {
585            try {
586                Thread.sleep(50);
587            } catch (Exception e) {
588                // Not really a problem since we're just going to sleep again.
589            }
590            maxms -= 50;
591        }
592        assertTrue(connection.mImeUpdateQueue.size() > 0);
593    }
594
595    private void assertClipboardContents(final Activity activity, final String expectedContents)
596            throws InterruptedException {
597        assertTrue(CriteriaHelper.pollForCriteria(new Criteria() {
598            @Override
599            public boolean isSatisfied() {
600                return ThreadUtils.runOnUiThreadBlockingNoException(new Callable<Boolean>() {
601                    @Override
602                    public Boolean call() throws Exception {
603                        ClipboardManager clipboardManager =
604                                (ClipboardManager) activity.getSystemService(
605                                        Context.CLIPBOARD_SERVICE);
606                        ClipData clip = clipboardManager.getPrimaryClip();
607                        return clip != null && clip.getItemCount() == 1
608                                && TextUtils.equals(clip.getItemAt(0).getText(), expectedContents);
609                    }
610                });
611            }
612        }));
613    }
614
615    private ImeAdapter getImeAdapter() {
616        return mContentViewCore.getImeAdapterForTest();
617    }
618
619    private AdapterInputConnection getAdapterInputConnection() {
620        return mContentViewCore.getInputConnectionForTest();
621    }
622
623    private void copy(final ImeAdapter adapter) {
624        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
625            @Override
626            public void run() {
627                adapter.copy();
628            }
629        });
630    }
631
632    private void cut(final ImeAdapter adapter) {
633        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
634            @Override
635            public void run() {
636                adapter.cut();
637            }
638        });
639    }
640
641    private void paste(final ImeAdapter adapter) {
642        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
643            @Override
644            public void run() {
645                adapter.paste();
646            }
647        });
648    }
649
650    private void selectAll(final ImeAdapter adapter) {
651        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
652            @Override
653            public void run() {
654                adapter.selectAll();
655            }
656        });
657    }
658
659    private void unselect(final ImeAdapter adapter) {
660        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
661            @Override
662            public void run() {
663                adapter.unselect();
664            }
665        });
666    }
667
668    private void commitText(final AdapterInputConnection connection, final CharSequence text,
669            final int newCursorPosition) {
670        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
671            @Override
672            public void run() {
673                connection.commitText(text, newCursorPosition);
674            }
675        });
676    }
677
678    private void setSelection(final AdapterInputConnection connection, final int start,
679            final int end) {
680        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
681            @Override
682            public void run() {
683                connection.setSelection(start, end);
684            }
685        });
686    }
687
688    private void setComposingRegion(final AdapterInputConnection connection, final int start,
689            final int end) {
690        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
691            @Override
692            public void run() {
693                connection.setComposingRegion(start, end);
694            }
695        });
696    }
697
698    private void setComposingText(final AdapterInputConnection connection, final CharSequence text,
699            final int newCursorPosition) {
700        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
701            @Override
702            public void run() {
703                connection.setComposingText(text, newCursorPosition);
704            }
705        });
706    }
707
708    private void finishComposingText(final AdapterInputConnection connection) {
709        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
710            @Override
711            public void run() {
712                connection.finishComposingText();
713            }
714        });
715    }
716
717    private void deleteSurroundingText(final AdapterInputConnection connection, final int before,
718            final int after) {
719        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
720            @Override
721            public void run() {
722                connection.deleteSurroundingText(before, after);
723            }
724        });
725    }
726
727    private static class TestAdapterInputConnectionFactory extends
728            ImeAdapter.AdapterInputConnectionFactory {
729        @Override
730        public AdapterInputConnection get(View view, ImeAdapter imeAdapter,
731                Editable editable, EditorInfo outAttrs) {
732            return new TestAdapterInputConnection(view, imeAdapter, editable, outAttrs);
733        }
734    }
735
736    private static class TestAdapterInputConnection extends AdapterInputConnection {
737        private final ArrayList<TestImeState> mImeUpdateQueue = new ArrayList<TestImeState>();
738
739        public TestAdapterInputConnection(View view, ImeAdapter imeAdapter,
740                Editable editable, EditorInfo outAttrs) {
741            super(view, imeAdapter, editable, outAttrs);
742        }
743
744        @Override
745        public void updateState(String text, int selectionStart, int selectionEnd,
746                int compositionStart, int compositionEnd, boolean requiredAck) {
747            mImeUpdateQueue.add(new TestImeState(text, selectionStart, selectionEnd,
748                    compositionStart, compositionEnd));
749            super.updateState(text, selectionStart, selectionEnd, compositionStart,
750                    compositionEnd, requiredAck);
751        }
752    }
753
754    private static class TestImeState {
755        private final String mText;
756        private final int mSelectionStart;
757        private final int mSelectionEnd;
758        private final int mCompositionStart;
759        private final int mCompositionEnd;
760
761        public TestImeState(String text, int selectionStart, int selectionEnd,
762                int compositionStart, int compositionEnd) {
763            mText = text;
764            mSelectionStart = selectionStart;
765            mSelectionEnd = selectionEnd;
766            mCompositionStart = compositionStart;
767            mCompositionEnd = compositionEnd;
768        }
769
770        public void assertEqualState(String text, int selectionStart, int selectionEnd,
771                int compositionStart, int compositionEnd) {
772            assertEquals("Text did not match", text, mText);
773            assertEquals("Selection start did not match", selectionStart, mSelectionStart);
774            assertEquals("Selection end did not match", selectionEnd, mSelectionEnd);
775            assertEquals("Composition start did not match", compositionStart, mCompositionStart);
776            assertEquals("Composition end did not match", compositionEnd, mCompositionEnd);
777        }
778    }
779}
780