Upstream version 7.36.149.0
[platform/framework/web/crosswalk.git] / src / content / public / android / javatests / src / org / chromium / content / browser / input / ImeTest.java
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
5 package org.chromium.content.browser.input;
6
7 import android.app.Activity;
8 import android.content.ClipData;
9 import android.content.ClipboardManager;
10 import android.content.Context;
11 import android.test.suitebuilder.annotation.MediumTest;
12 import android.test.suitebuilder.annotation.SmallTest;
13 import android.text.Editable;
14 import android.text.TextUtils;
15 import android.view.KeyEvent;
16 import android.view.View;
17 import android.view.inputmethod.EditorInfo;
18
19 import org.chromium.base.ThreadUtils;
20 import org.chromium.base.test.util.Feature;
21 import org.chromium.base.test.util.UrlUtils;
22 import org.chromium.content.browser.ContentViewCore;
23 import org.chromium.content.browser.test.util.Criteria;
24 import org.chromium.content.browser.test.util.CriteriaHelper;
25 import org.chromium.content.browser.test.util.DOMUtils;
26 import org.chromium.content.browser.test.util.TestCallbackHelperContainer;
27 import org.chromium.content.browser.test.util.TestInputMethodManagerWrapper;
28 import org.chromium.content_shell_apk.ContentShellTestBase;
29
30 import java.util.ArrayList;
31 import java.util.concurrent.Callable;
32
33 /**
34  * Integration tests for text input using cases based on fixed regressions.
35  */
36 public class ImeTest extends ContentShellTestBase {
37
38     private static final String DATA_URL = UrlUtils.encodeHtmlDataUri(
39             "<html><head><meta name=\"viewport\"" +
40             "content=\"width=device-width, initial-scale=2.0, maximum-scale=2.0\" /></head>" +
41             "<body><form action=\"about:blank\">" +
42             "<input id=\"input_text\" type=\"text\" /><br/>" +
43             "<input id=\"input_radio\" type=\"radio\" style=\"width:50px;height:50px\" />" +
44             "<br/><textarea id=\"textarea\" rows=\"4\" cols=\"20\"></textarea>" +
45             "</form></body></html>");
46
47     private TestAdapterInputConnection mConnection;
48     private ImeAdapter mImeAdapter;
49
50     private ContentViewCore mContentViewCore;
51     private TestCallbackHelperContainer mCallbackContainer;
52     private TestInputMethodManagerWrapper mInputMethodManagerWrapper;
53
54     @Override
55     public void setUp() throws Exception {
56         super.setUp();
57
58         launchContentShellWithUrl(DATA_URL);
59         assertTrue("Page failed to load", waitForActiveShellToBeDoneLoading());
60         mContentViewCore = getContentViewCore();
61
62         mInputMethodManagerWrapper = new TestInputMethodManagerWrapper(mContentViewCore);
63         getImeAdapter().setInputMethodManagerWrapper(mInputMethodManagerWrapper);
64         assertEquals(0, mInputMethodManagerWrapper.getShowSoftInputCounter());
65         mContentViewCore.setAdapterInputConnectionFactory(
66                 new TestAdapterInputConnectionFactory());
67
68         mCallbackContainer = new TestCallbackHelperContainer(mContentViewCore);
69         // TODO(aurimas) remove this wait once crbug.com/179511 is fixed.
70         assertWaitForPageScaleFactorMatch(2);
71         assertTrue(DOMUtils.waitForNonZeroNodeBounds(
72                 mContentViewCore, "input_text"));
73         DOMUtils.clickNode(this, mContentViewCore, "input_text");
74         assertWaitForKeyboardStatus(true);
75
76         mConnection = (TestAdapterInputConnection) getAdapterInputConnection();
77         mImeAdapter = getImeAdapter();
78
79         waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 0, "", 0, 0, -1, -1);
80         assertEquals(1, mInputMethodManagerWrapper.getShowSoftInputCounter());
81         assertEquals(0, mInputMethodManagerWrapper.getEditorInfo().initialSelStart);
82         assertEquals(0, mInputMethodManagerWrapper.getEditorInfo().initialSelEnd);
83     }
84
85     @MediumTest
86     @Feature({"TextInput", "Main"})
87     public void testKeyboardDismissedAfterClickingGo() throws Throwable {
88         setComposingText(mConnection, "hello", 1);
89         waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 1, "hello", 5, 5, 0, 5);
90
91         performGo(getAdapterInputConnection(), mCallbackContainer);
92
93         waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 2, "", 0, 0, -1, -1);
94         assertWaitForKeyboardStatus(false);
95     }
96
97     @SmallTest
98     @Feature({"TextInput", "Main"})
99     public void testGetTextUpdatesAfterEnteringText() throws Throwable {
100         setComposingText(mConnection, "h", 1);
101         waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 1, "h", 1, 1, 0, 1);
102         assertEquals(1, mInputMethodManagerWrapper.getShowSoftInputCounter());
103
104         setComposingText(mConnection, "he", 1);
105         waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 2, "he", 2, 2, 0, 2);
106         assertEquals(1, mInputMethodManagerWrapper.getShowSoftInputCounter());
107
108         setComposingText(mConnection, "hel", 1);
109         waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 3, "hel", 3, 3, 0, 3);
110         assertEquals(1, mInputMethodManagerWrapper.getShowSoftInputCounter());
111
112         commitText(mConnection, "hel", 1);
113         waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 4, "hel", 3, 3, -1, -1);
114         assertEquals(1, mInputMethodManagerWrapper.getShowSoftInputCounter());
115     }
116
117     @SmallTest
118     @Feature({"TextInput"})
119     public void testImeCopy() throws Exception {
120         commitText(mConnection, "hello", 1);
121         waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 1, "hello", 5, 5, -1, -1);
122
123         setSelection(mConnection, 2, 5);
124         waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 2, "hello", 2, 5, -1, -1);
125
126         copy(mImeAdapter);
127         assertClipboardContents(getActivity(), "llo");
128     }
129
130     @SmallTest
131     @Feature({"TextInput"})
132     public void testEnterTextAndRefocus() throws Exception {
133         commitText(mConnection, "hello", 1);
134         waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 1, "hello", 5, 5, -1, -1);
135
136         DOMUtils.clickNode(this, mContentViewCore, "input_radio");
137         assertWaitForKeyboardStatus(false);
138
139         DOMUtils.clickNode(this, mContentViewCore, "input_text");
140         assertWaitForKeyboardStatus(true);
141         assertEquals(5, mInputMethodManagerWrapper.getEditorInfo().initialSelStart);
142         assertEquals(5, mInputMethodManagerWrapper.getEditorInfo().initialSelEnd);
143     }
144
145     @SmallTest
146     @Feature({"TextInput"})
147     public void testImeCut() throws Exception {
148         commitText(mConnection, "snarful", 1);
149         waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 1, "snarful", 7, 7, -1, -1);
150
151         setSelection(mConnection, 1, 5);
152         waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 2, "snarful", 1, 5, -1, -1);
153
154         cut(mImeAdapter);
155         waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 3, "sul", 1, 1, -1, -1);
156
157         assertClipboardContents(getActivity(), "narf");
158     }
159
160     @SmallTest
161     @Feature({"TextInput"})
162     public void testImePaste() throws Exception {
163         ThreadUtils.runOnUiThreadBlocking(new Runnable() {
164             @Override
165             public void run() {
166                 ClipboardManager clipboardManager =
167                         (ClipboardManager) getActivity().getSystemService(
168                                 Context.CLIPBOARD_SERVICE);
169                 clipboardManager.setPrimaryClip(ClipData.newPlainText("blarg", "blarg"));
170             }
171         });
172
173         paste(mImeAdapter);
174         waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 1, "blarg", 5, 5, -1, -1);
175
176         setSelection(mConnection, 3, 5);
177         waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 2, "blarg", 3, 5, -1, -1);
178
179         paste(mImeAdapter);
180         // Paste is a two step process when there is a non-zero selection.
181         waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 3, "bla", 3, 3, -1, -1);
182         waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 4, "blablarg", 8, 8, -1, -1);
183
184         paste(mImeAdapter);
185         waitAndVerifyEditableCallback(
186                 mConnection.mImeUpdateQueue, 5, "blablargblarg", 13, 13, -1, -1);
187     }
188
189     @SmallTest
190     @Feature({"TextInput"})
191     public void testImeSelectAndUnSelectAll() throws Exception {
192         commitText(mConnection, "hello", 1);
193         waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 1, "hello", 5, 5, -1, -1);
194
195         selectAll(mImeAdapter);
196         waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 2, "hello", 0, 5, -1, -1);
197
198         unselect(mImeAdapter);
199         waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 3, "", 0, 0, -1, -1);
200
201         assertWaitForKeyboardStatus(false);
202     }
203
204     @SmallTest
205     @Feature({"TextInput", "Main"})
206     public void testShowImeIfNeeded() throws Throwable {
207         DOMUtils.focusNode(mContentViewCore, "input_radio");
208         assertWaitForKeyboardStatus(false);
209
210         performShowImeIfNeeded();
211         assertWaitForKeyboardStatus(false);
212
213         DOMUtils.focusNode(mContentViewCore, "input_text");
214         assertWaitForKeyboardStatus(false);
215
216         performShowImeIfNeeded();
217         assertWaitForKeyboardStatus(true);
218     }
219
220     @SmallTest
221     @Feature({"TextInput", "Main"})
222     public void testFinishComposingText() throws Throwable {
223         // Focus the textarea. We need to do the following steps because we are focusing using JS.
224         DOMUtils.focusNode(mContentViewCore, "input_radio");
225         assertWaitForKeyboardStatus(false);
226         DOMUtils.focusNode(mContentViewCore, "textarea");
227         assertWaitForKeyboardStatus(false);
228         performShowImeIfNeeded();
229         assertWaitForKeyboardStatus(true);
230
231         mConnection = (TestAdapterInputConnection) getAdapterInputConnection();
232         waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 0, "", 0, 0, -1, -1);
233
234         commitText(mConnection, "hllo", 1);
235         waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 1, "hllo", 4, 4, -1, -1);
236
237         commitText(mConnection, " ", 1);
238         waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 2, "hllo ", 5, 5, -1, -1);
239
240         setSelection(mConnection, 1, 1);
241         waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 3, "hllo ", 1, 1, -1, -1);
242
243         setComposingRegion(mConnection, 0, 4);
244         waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 4, "hllo ", 1, 1, 0, 4);
245
246         finishComposingText(mConnection);
247         waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 5, "hllo ", 1, 1, -1, -1);
248
249         commitText(mConnection, "\n", 1);
250         waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 6, "h\nllo ", 2, 2, -1, -1);
251     }
252
253     @SmallTest
254     @Feature({"TextInput", "Main"})
255     public void testEnterKeyEventWhileComposingText() throws Throwable {
256         // Focus the textarea. We need to do the following steps because we are focusing using JS.
257         DOMUtils.focusNode(mContentViewCore, "input_radio");
258         assertWaitForKeyboardStatus(false);
259         DOMUtils.focusNode(mContentViewCore, "textarea");
260         assertWaitForKeyboardStatus(false);
261         performShowImeIfNeeded();
262         assertWaitForKeyboardStatus(true);
263
264         mConnection = (TestAdapterInputConnection) getAdapterInputConnection();
265         waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 0, "", 0, 0, -1, -1);
266
267         setComposingText(mConnection, "hello", 1);
268         waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 1, "hello", 5, 5, 0, 5);
269
270         getInstrumentation().runOnMainSync(new Runnable() {
271             @Override
272             public void run() {
273                 mConnection.sendKeyEvent(
274                         new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER));
275                 mConnection.sendKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER));
276             }
277         });
278
279         // TODO(aurimas): remove this workaround when crbug.com/278584 is fixed.
280         waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 2, "hello", 5, 5, -1, -1);
281         // The second new line is not a user visible/editable one, it is a side-effect of Blink
282         // using <br> internally.
283         waitAndVerifyEditableCallback(mConnection.mImeUpdateQueue, 3, "hello\n\n", 6, 6, -1, -1);
284     }
285
286     private void performShowImeIfNeeded() {
287         ThreadUtils.runOnUiThreadBlocking(new Runnable() {
288             @Override
289             public void run() {
290                 mContentViewCore.showImeIfNeeded();
291             }
292         });
293     }
294
295     private void performGo(final AdapterInputConnection inputConnection,
296             TestCallbackHelperContainer testCallbackHelperContainer) throws Throwable {
297         handleBlockingCallbackAction(
298                 testCallbackHelperContainer.getOnPageFinishedHelper(),
299                 new Runnable() {
300                     @Override
301                     public void run() {
302                         inputConnection.performEditorAction(EditorInfo.IME_ACTION_GO);
303                     }
304                 });
305     }
306
307     private void assertWaitForKeyboardStatus(final boolean show) throws InterruptedException {
308         assertTrue(CriteriaHelper.pollForCriteria(new Criteria() {
309             @Override
310             public boolean isSatisfied() {
311                 return show == getImeAdapter().mIsShowWithoutHideOutstanding &&
312                         (!show || getAdapterInputConnection() != null);
313             }
314         }));
315     }
316
317     private void waitAndVerifyEditableCallback(final ArrayList<TestImeState> states,
318             final int index, String text, int selectionStart, int selectionEnd,
319             int compositionStart, int compositionEnd) throws InterruptedException {
320         assertTrue(CriteriaHelper.pollForCriteria(new Criteria() {
321             @Override
322             public boolean isSatisfied() {
323                 return states.size() > index;
324             }
325         }));
326         states.get(index).assertEqualState(
327                 text, selectionStart, selectionEnd, compositionStart, compositionEnd);
328     }
329
330     private void assertClipboardContents(final Activity activity, final String expectedContents)
331             throws InterruptedException {
332         assertTrue(CriteriaHelper.pollForCriteria(new Criteria() {
333             @Override
334             public boolean isSatisfied() {
335                 return ThreadUtils.runOnUiThreadBlockingNoException(new Callable<Boolean>() {
336                     @Override
337                     public Boolean call() throws Exception {
338                         ClipboardManager clipboardManager =
339                                 (ClipboardManager) activity.getSystemService(
340                                         Context.CLIPBOARD_SERVICE);
341                         ClipData clip = clipboardManager.getPrimaryClip();
342                         return clip != null && clip.getItemCount() == 1
343                                 && TextUtils.equals(clip.getItemAt(0).getText(), expectedContents);
344                     }
345                 });
346             }
347         }));
348     }
349
350     private ImeAdapter getImeAdapter() {
351         return mContentViewCore.getImeAdapterForTest();
352     }
353
354     private AdapterInputConnection getAdapterInputConnection() {
355         return mContentViewCore.getInputConnectionForTest();
356     }
357
358     private void copy(final ImeAdapter adapter) {
359         ThreadUtils.runOnUiThreadBlocking(new Runnable() {
360             @Override
361             public void run() {
362                 adapter.copy();
363             }
364         });
365     }
366
367     private void cut(final ImeAdapter adapter) {
368         ThreadUtils.runOnUiThreadBlocking(new Runnable() {
369             @Override
370             public void run() {
371                 adapter.cut();
372             }
373         });
374     }
375
376     private void paste(final ImeAdapter adapter) {
377         ThreadUtils.runOnUiThreadBlocking(new Runnable() {
378             @Override
379             public void run() {
380                 adapter.paste();
381             }
382         });
383     }
384
385     private void selectAll(final ImeAdapter adapter) {
386         ThreadUtils.runOnUiThreadBlocking(new Runnable() {
387             @Override
388             public void run() {
389                 adapter.selectAll();
390             }
391         });
392     }
393
394     private void unselect(final ImeAdapter adapter) {
395         ThreadUtils.runOnUiThreadBlocking(new Runnable() {
396             @Override
397             public void run() {
398                 adapter.unselect();
399             }
400         });
401     }
402
403     private void commitText(final AdapterInputConnection connection, final CharSequence text,
404             final int newCursorPosition) {
405         ThreadUtils.runOnUiThreadBlocking(new Runnable() {
406             @Override
407             public void run() {
408                 connection.commitText(text, newCursorPosition);
409             }
410         });
411     }
412
413     private void setSelection(final AdapterInputConnection connection, final int start,
414             final int end) {
415         ThreadUtils.runOnUiThreadBlocking(new Runnable() {
416             @Override
417             public void run() {
418                 connection.setSelection(start, end);
419             }
420         });
421     }
422
423     private void setComposingRegion(final AdapterInputConnection connection, final int start,
424             final int end) {
425         ThreadUtils.runOnUiThreadBlocking(new Runnable() {
426             @Override
427             public void run() {
428                 connection.setComposingRegion(start, end);
429             }
430         });
431     }
432
433     private void setComposingText(final AdapterInputConnection connection, final CharSequence text,
434             final int newCursorPosition) {
435         ThreadUtils.runOnUiThreadBlocking(new Runnable() {
436             @Override
437             public void run() {
438                 connection.setComposingText(text, newCursorPosition);
439             }
440         });
441     }
442
443     private void finishComposingText(final AdapterInputConnection connection) {
444         ThreadUtils.runOnUiThreadBlocking(new Runnable() {
445             @Override
446             public void run() {
447                 connection.finishComposingText();
448             }
449         });
450     }
451
452     private static class TestAdapterInputConnectionFactory extends
453             ImeAdapter.AdapterInputConnectionFactory {
454         @Override
455         public AdapterInputConnection get(View view, ImeAdapter imeAdapter,
456                 Editable editable, EditorInfo outAttrs) {
457             return new TestAdapterInputConnection(view, imeAdapter, editable, outAttrs);
458         }
459     }
460
461     private static class TestAdapterInputConnection extends AdapterInputConnection {
462         private final ArrayList<TestImeState> mImeUpdateQueue = new ArrayList<TestImeState>();
463
464         public TestAdapterInputConnection(View view, ImeAdapter imeAdapter,
465                 Editable editable, EditorInfo outAttrs) {
466             super(view, imeAdapter, editable, outAttrs);
467         }
468
469         @Override
470         public void updateState(String text, int selectionStart, int selectionEnd,
471                 int compositionStart, int compositionEnd, boolean requiredAck) {
472             mImeUpdateQueue.add(new TestImeState(text, selectionStart, selectionEnd,
473                     compositionStart, compositionEnd));
474             super.updateState(text, selectionStart, selectionEnd, compositionStart,
475                     compositionEnd, requiredAck);
476         }
477     }
478
479     private static class TestImeState {
480         private final String mText;
481         private final int mSelectionStart;
482         private final int mSelectionEnd;
483         private final int mCompositionStart;
484         private final int mCompositionEnd;
485
486         public TestImeState(String text, int selectionStart, int selectionEnd,
487                 int compositionStart, int compositionEnd) {
488             mText = text;
489             mSelectionStart = selectionStart;
490             mSelectionEnd = selectionEnd;
491             mCompositionStart = compositionStart;
492             mCompositionEnd = compositionEnd;
493         }
494
495         public void assertEqualState(String text, int selectionStart, int selectionEnd,
496                 int compositionStart, int compositionEnd) {
497             assertEquals("Text did not match", text, mText);
498             assertEquals("Selection start did not match", selectionStart, mSelectionStart);
499             assertEquals("Selection end did not match", selectionEnd, mSelectionEnd);
500             assertEquals("Composition start did not match", compositionStart, mCompositionStart);
501             assertEquals("Composition end did not match", compositionEnd, mCompositionEnd);
502         }
503     }
504 }