ImeTest.java revision c5cede9ae108bb15f6b7a8aea21c7e1fefa2834c
19720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Block// Copyright 2013 The Chromium Authors. All rights reserved.
29720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Block// Use of this source code is governed by a BSD-style license that can be
39720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Block// found in the LICENSE file.
49720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Block
59720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Blockpackage org.chromium.content.browser.input;
69720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Block
79720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Blockimport android.app.Activity;
89720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Blockimport android.content.ClipData;
99720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Blockimport android.content.ClipboardManager;
109720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Blockimport android.content.Context;
119720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Blockimport android.test.suitebuilder.annotation.MediumTest;
129720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Blockimport android.test.suitebuilder.annotation.SmallTest;
139720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Blockimport android.text.Editable;
149720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Blockimport android.text.TextUtils;
159720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Blockimport android.view.KeyEvent;
169720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Blockimport android.view.View;
179720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Blockimport android.view.inputmethod.EditorInfo;
189720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Block
199720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Blockimport org.chromium.base.ThreadUtils;
209720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Blockimport org.chromium.base.test.util.Feature;
219720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Blockimport org.chromium.base.test.util.UrlUtils;
229720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Blockimport org.chromium.content.browser.ContentView;
239720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Blockimport org.chromium.content.browser.ContentViewCore;
249720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Blockimport org.chromium.content.browser.test.util.Criteria;
259720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Blockimport org.chromium.content.browser.test.util.CriteriaHelper;
269720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Blockimport org.chromium.content.browser.test.util.DOMUtils;
279720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Blockimport org.chromium.content.browser.test.util.TestCallbackHelperContainer;
289720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Blockimport org.chromium.content.browser.test.util.TestInputMethodManagerWrapper;
299720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Blockimport org.chromium.content_shell_apk.ContentShellTestBase;
309720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Block
319720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Blockimport java.util.ArrayList;
329720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Blockimport java.util.concurrent.Callable;
339720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Block
349720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Block/**
359720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Block * Integration tests for text input using cases based on fixed regressions.
369720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Block */
379720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Blockpublic class ImeTest extends ContentShellTestBase {
389720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Block
399720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Block    private static final String DATA_URL = UrlUtils.encodeHtmlDataUri(
409720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Block            "<html><head><meta name=\"viewport\"" +
419720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Block            "content=\"width=device-width, initial-scale=2.0, maximum-scale=2.0\" /></head>" +
429720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Block            "<body><form action=\"about:blank\">" +
439720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Block            "<input id=\"input_text\" type=\"text\" /><br/>" +
449720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Block            "<input id=\"input_radio\" type=\"radio\" style=\"width:50px;height:50px\" />" +
459720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Block            "<br/><textarea id=\"textarea\" rows=\"4\" cols=\"20\"></textarea>" +
469720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Block            "</form></body></html>");
479720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Block
489720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Block    private TestAdapterInputConnection mConnection;
499720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Block    private ImeAdapter mImeAdapter;
509720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Block
519720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Block    private ContentView mContentView;
529720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Block    private ContentViewCore mContentViewCore;
539720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Block    private TestCallbackHelperContainer mCallbackContainer;
549720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Block    private TestInputMethodManagerWrapper mInputMethodManagerWrapper;
559720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Block
569720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Block    @Override
579720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Block    public void setUp() throws Exception {
589720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Block        super.setUp();
599720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Block
609720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Block        launchContentShellWithUrl(DATA_URL);
619720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Block        assertTrue("Page failed to load", waitForActiveShellToBeDoneLoading());
629720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Block        mContentView = getContentView();
639720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Block        mContentViewCore = getContentViewCore();
649720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Block
659720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Block        mInputMethodManagerWrapper = new TestInputMethodManagerWrapper(mContentViewCore);
669720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Block        getImeAdapter().setInputMethodManagerWrapper(mInputMethodManagerWrapper);
679720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Block        assertEquals(0, mInputMethodManagerWrapper.getShowSoftInputCounter());
689720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Block        mContentViewCore.setAdapterInputConnectionFactory(
699720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Block                new TestAdapterInputConnectionFactory());
709720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Block
719720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Block        mCallbackContainer = new TestCallbackHelperContainer(getContentView());
729720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Block        // TODO(aurimas) remove this wait once crbug.com/179511 is fixed.
739720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Block        assertWaitForPageScaleFactorMatch(2);
749720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Block        assertTrue(DOMUtils.waitForNonZeroNodeBounds(
759720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Block                mContentViewCore, "input_text"));
769720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Block        DOMUtils.clickNode(this, mContentView, "input_text");
779720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Block        assertWaitForKeyboardStatus(true);
789720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Block
799720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Block        mConnection = (TestAdapterInputConnection) getAdapterInputConnection();
809720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Block        mImeAdapter = getImeAdapter();
819720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Block
829720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Block        waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 0, "", 0, 0, -1, -1);
839720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Block        assertEquals(1, mInputMethodManagerWrapper.getShowSoftInputCounter());
849720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Block        assertEquals(0, mInputMethodManagerWrapper.getEditorInfo().initialSelStart);
859720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Block        assertEquals(0, mInputMethodManagerWrapper.getEditorInfo().initialSelEnd);
869720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Block    }
879720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Block
889720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Block    @MediumTest
899720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Block    @Feature({"TextInput", "Main"})
909720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Block    public void testKeyboardDismissedAfterClickingGo() throws Throwable {
919720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Block        setComposingText(mConnection, "hello", 1);
929720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Block        waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 1, "hello", 5, 5, 0, 5);
939720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Block
949720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Block        performGo(getAdapterInputConnection(), mCallbackContainer);
959720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Block
969720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Block        waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 2, "", 0, 0, -1, -1);
979720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Block        assertWaitForKeyboardStatus(false);
989720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Block    }
999720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Block
1009720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Block    @SmallTest
1019720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Block    @Feature({"TextInput", "Main"})
1029720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Block    public void testGetTextUpdatesAfterEnteringText() throws Throwable {
1039720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Block        setComposingText(mConnection, "h", 1);
1049720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Block        waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 1, "h", 1, 1, 0, 1);
1059720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Block        assertEquals(1, mInputMethodManagerWrapper.getShowSoftInputCounter());
1069720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Block
1079720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Block        setComposingText(mConnection, "he", 1);
1089720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Block        waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 2, "he", 2, 2, 0, 2);
1099720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Block        assertEquals(1, mInputMethodManagerWrapper.getShowSoftInputCounter());
1109720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Block
1119720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Block        setComposingText(mConnection, "hel", 1);
1129720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Block        waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 3, "hel", 3, 3, 0, 3);
1139720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Block        assertEquals(1, mInputMethodManagerWrapper.getShowSoftInputCounter());
1149720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Block
1159720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Block        commitText(mConnection, "hel", 1);
1169720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Block        waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 4, "hel", 3, 3, -1, -1);
1179720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Block        assertEquals(1, mInputMethodManagerWrapper.getShowSoftInputCounter());
1189720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Block    }
1199720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Block
1209720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Block    @SmallTest
1219720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Block    @Feature({"TextInput"})
1229720d5f59b9c1f5d1b9ecbc9173dbcb71bd557beSteve Block    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, mContentView, "input_radio");
140        assertWaitForKeyboardStatus(false);
141
142        DOMUtils.clickNode(this, mContentView, "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 testImeCut() throws Exception {
151        commitText(mConnection, "snarful", 1);
152        waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 1, "snarful", 7, 7, -1, -1);
153
154        setSelection(mConnection, 1, 5);
155        waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 2, "snarful", 1, 5, -1, -1);
156
157        cut(mImeAdapter);
158        waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 3, "sul", 1, 1, -1, -1);
159
160        assertClipboardContents(getActivity(), "narf");
161    }
162
163    @SmallTest
164    @Feature({"TextInput"})
165    public void testImePaste() throws Exception {
166        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
167            @Override
168            public void run() {
169                ClipboardManager clipboardManager =
170                        (ClipboardManager) getActivity().getSystemService(
171                                Context.CLIPBOARD_SERVICE);
172                clipboardManager.setPrimaryClip(ClipData.newPlainText("blarg", "blarg"));
173            }
174        });
175
176        paste(mImeAdapter);
177        waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 1, "blarg", 5, 5, -1, -1);
178
179        setSelection(mConnection, 3, 5);
180        waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 2, "blarg", 3, 5, -1, -1);
181
182        paste(mImeAdapter);
183        // Paste is a two step process when there is a non-zero selection.
184        waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 3, "bla", 3, 3, -1, -1);
185        waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 4, "blablarg", 8, 8, -1, -1);
186
187        paste(mImeAdapter);
188        waitAndVerifyEditableCallback(
189                mConnection.mImeUpdateQueue, 5, "blablargblarg", 13, 13, -1, -1);
190    }
191
192    @SmallTest
193    @Feature({"TextInput"})
194    public void testImeSelectAndUnSelectAll() throws Exception {
195        commitText(mConnection, "hello", 1);
196        waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 1, "hello", 5, 5, -1, -1);
197
198        selectAll(mImeAdapter);
199        waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 2, "hello", 0, 5, -1, -1);
200
201        unselect(mImeAdapter);
202        waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 3, "", 0, 0, -1, -1);
203
204        assertWaitForKeyboardStatus(false);
205    }
206
207    @SmallTest
208    @Feature({"TextInput", "Main"})
209    public void testShowImeIfNeeded() throws Throwable {
210        DOMUtils.focusNode(this, mContentViewCore, "input_radio");
211        assertWaitForKeyboardStatus(false);
212
213        performShowImeIfNeeded();
214        assertWaitForKeyboardStatus(false);
215
216        DOMUtils.focusNode(this, mContentViewCore, "input_text");
217        assertWaitForKeyboardStatus(false);
218
219        performShowImeIfNeeded();
220        assertWaitForKeyboardStatus(true);
221    }
222
223    @SmallTest
224    @Feature({"TextInput", "Main"})
225    public void testFinishComposingText() throws Throwable {
226        // Focus the textarea. We need to do the following steps because we are focusing using JS.
227        DOMUtils.focusNode(this, mContentViewCore, "input_radio");
228        assertWaitForKeyboardStatus(false);
229        DOMUtils.focusNode(this, mContentViewCore, "textarea");
230        assertWaitForKeyboardStatus(false);
231        performShowImeIfNeeded();
232        assertWaitForKeyboardStatus(true);
233
234        mConnection = (TestAdapterInputConnection) getAdapterInputConnection();
235        waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 0, "", 0, 0, -1, -1);
236
237        commitText(mConnection, "hllo", 1);
238        waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 1, "hllo", 4, 4, -1, -1);
239
240        commitText(mConnection, " ", 1);
241        waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 2, "hllo ", 5, 5, -1, -1);
242
243        setSelection(mConnection, 1, 1);
244        waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 3, "hllo ", 1, 1, -1, -1);
245
246        setComposingRegion(mConnection, 0, 4);
247        waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 4, "hllo ", 1, 1, 0, 4);
248
249        finishComposingText(mConnection);
250        waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 5, "hllo ", 1, 1, -1, -1);
251
252        commitText(mConnection, "\n", 1);
253        waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 6, "h\nllo ", 2, 2, -1, -1);
254    }
255
256    @SmallTest
257    @Feature({"TextInput", "Main"})
258    public void testEnterKeyEventWhileComposingText() throws Throwable {
259        // Focus the textarea. We need to do the following steps because we are focusing using JS.
260        DOMUtils.focusNode(this, mContentViewCore, "input_radio");
261        assertWaitForKeyboardStatus(false);
262        DOMUtils.focusNode(this, mContentViewCore, "textarea");
263        assertWaitForKeyboardStatus(false);
264        performShowImeIfNeeded();
265        assertWaitForKeyboardStatus(true);
266
267        mConnection = (TestAdapterInputConnection) getAdapterInputConnection();
268        waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 0, "", 0, 0, -1, -1);
269
270        setComposingText(mConnection, "hello", 1);
271        waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 1, "hello", 5, 5, 0, 5);
272
273        getInstrumentation().runOnMainSync(new Runnable() {
274            @Override
275            public void run() {
276                mConnection.sendKeyEvent(
277                        new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER));
278                mConnection.sendKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER));
279            }
280        });
281
282        // TODO(aurimas): remove this workaround when crbug.com/278584 is fixed.
283        waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 2, "hello", 5, 5, -1, -1);
284        // The second new line is not a user visible/editable one, it is a side-effect of Blink
285        // using <br> internally.
286        waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 3, "hello\n\n", 6, 6, -1, -1);
287    }
288
289    private void performShowImeIfNeeded() {
290        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
291            @Override
292            public void run() {
293                mContentViewCore.showImeIfNeeded();
294            }
295        });
296    }
297
298    private void performGo(final AdapterInputConnection inputConnection,
299            TestCallbackHelperContainer testCallbackHelperContainer) throws Throwable {
300        handleBlockingCallbackAction(
301                testCallbackHelperContainer.getOnPageFinishedHelper(),
302                new Runnable() {
303                    @Override
304                    public void run() {
305                        inputConnection.performEditorAction(EditorInfo.IME_ACTION_GO);
306                    }
307                });
308    }
309
310    private void assertWaitForKeyboardStatus(final boolean show) throws InterruptedException {
311        assertTrue(CriteriaHelper.pollForCriteria(new Criteria() {
312            @Override
313            public boolean isSatisfied() {
314                return show == getImeAdapter().mIsShowWithoutHideOutstanding &&
315                        (!show || getAdapterInputConnection() != null);
316            }
317        }));
318    }
319
320    private void waitAndVerifyEditableCallback(final ArrayList<TestImeState> states,
321            final int index, String text, int selectionStart, int selectionEnd,
322            int compositionStart, int compositionEnd) throws InterruptedException {
323        assertTrue(CriteriaHelper.pollForCriteria(new Criteria() {
324            @Override
325            public boolean isSatisfied() {
326                return states.size() > index;
327            }
328        }));
329        states.get(index).assertEqualState(
330                text, selectionStart, selectionEnd, compositionStart, compositionEnd);
331    }
332
333    private void assertClipboardContents(final Activity activity, final String expectedContents)
334            throws InterruptedException {
335        assertTrue(CriteriaHelper.pollForCriteria(new Criteria() {
336            @Override
337            public boolean isSatisfied() {
338                return ThreadUtils.runOnUiThreadBlockingNoException(new Callable<Boolean>() {
339                    @Override
340                    public Boolean call() throws Exception {
341                        ClipboardManager clipboardManager =
342                                (ClipboardManager) activity.getSystemService(
343                                        Context.CLIPBOARD_SERVICE);
344                        ClipData clip = clipboardManager.getPrimaryClip();
345                        return clip != null && clip.getItemCount() == 1
346                                && TextUtils.equals(clip.getItemAt(0).getText(), expectedContents);
347                    }
348                });
349            }
350        }));
351    }
352
353    private ImeAdapter getImeAdapter() {
354        return mContentViewCore.getImeAdapterForTest();
355    }
356
357    private AdapterInputConnection getAdapterInputConnection() {
358        return mContentViewCore.getInputConnectionForTest();
359    }
360
361    private void copy(final ImeAdapter adapter) {
362        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
363            @Override
364            public void run() {
365                adapter.copy();
366            }
367        });
368    }
369
370    private void cut(final ImeAdapter adapter) {
371        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
372            @Override
373            public void run() {
374                adapter.cut();
375            }
376        });
377    }
378
379    private void paste(final ImeAdapter adapter) {
380        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
381            @Override
382            public void run() {
383                adapter.paste();
384            }
385        });
386    }
387
388    private void selectAll(final ImeAdapter adapter) {
389        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
390            @Override
391            public void run() {
392                adapter.selectAll();
393            }
394        });
395    }
396
397    private void unselect(final ImeAdapter adapter) {
398        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
399            @Override
400            public void run() {
401                adapter.unselect();
402            }
403        });
404    }
405
406    private void commitText(final AdapterInputConnection connection, final CharSequence text,
407            final int newCursorPosition) {
408        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
409            @Override
410            public void run() {
411                connection.commitText(text, newCursorPosition);
412            }
413        });
414    }
415
416    private void setSelection(final AdapterInputConnection connection, final int start,
417            final int end) {
418        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
419            @Override
420            public void run() {
421                connection.setSelection(start, end);
422            }
423        });
424    }
425
426    private void setComposingRegion(final AdapterInputConnection connection, final int start,
427            final int end) {
428        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
429            @Override
430            public void run() {
431                connection.setComposingRegion(start, end);
432            }
433        });
434    }
435
436    private void setComposingText(final AdapterInputConnection connection, final CharSequence text,
437            final int newCursorPosition) {
438        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
439            @Override
440            public void run() {
441                connection.setComposingText(text, newCursorPosition);
442            }
443        });
444    }
445
446    private void finishComposingText(final AdapterInputConnection connection) {
447        ThreadUtils.runOnUiThreadBlocking(new Runnable() {
448            @Override
449            public void run() {
450                connection.finishComposingText();
451            }
452        });
453    }
454
455    private static class TestAdapterInputConnectionFactory extends
456            ImeAdapter.AdapterInputConnectionFactory {
457        @Override
458        public AdapterInputConnection get(View view, ImeAdapter imeAdapter,
459                Editable editable, EditorInfo outAttrs) {
460            return new TestAdapterInputConnection(view, imeAdapter, editable, outAttrs);
461        }
462    }
463
464    private static class TestAdapterInputConnection extends AdapterInputConnection {
465        private final ArrayList<TestImeState> mImeUpdateQueue = new ArrayList<TestImeState>();
466
467        public TestAdapterInputConnection(View view, ImeAdapter imeAdapter,
468                Editable editable, EditorInfo outAttrs) {
469            super(view, imeAdapter, editable, outAttrs);
470        }
471
472        @Override
473        public void updateState(String text, int selectionStart, int selectionEnd,
474                int compositionStart, int compositionEnd, boolean requiredAck) {
475            mImeUpdateQueue.add(new TestImeState(text, selectionStart, selectionEnd,
476                    compositionStart, compositionEnd));
477            super.updateState(text, selectionStart, selectionEnd, compositionStart,
478                    compositionEnd, requiredAck);
479        }
480    }
481
482    private static class TestImeState {
483        private final String mText;
484        private final int mSelectionStart;
485        private final int mSelectionEnd;
486        private final int mCompositionStart;
487        private final int mCompositionEnd;
488
489        public TestImeState(String text, int selectionStart, int selectionEnd,
490                int compositionStart, int compositionEnd) {
491            mText = text;
492            mSelectionStart = selectionStart;
493            mSelectionEnd = selectionEnd;
494            mCompositionStart = compositionStart;
495            mCompositionEnd = compositionEnd;
496        }
497
498        public void assertEqualState(String text, int selectionStart, int selectionEnd,
499                int compositionStart, int compositionEnd) {
500            assertEquals("Text did not match", text, mText);
501            assertEquals("Selection start did not match", selectionStart, mSelectionStart);
502            assertEquals("Selection end did not match", selectionEnd, mSelectionEnd);
503            assertEquals("Composition start did not match", compositionStart, mCompositionStart);
504            assertEquals("Composition end did not match", compositionEnd, mCompositionEnd);
505        }
506    }
507}
508