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