Upstream version 7.36.149.0
[platform/framework/web/crosswalk.git] / src / content / public / android / javatests / src / org / chromium / content / browser / input / InsertionHandleTest.java
1 // Copyright 2012 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.content.ClipData;
8 import android.content.ClipboardManager;
9 import android.content.Context;
10 import android.graphics.Rect;
11 import android.test.suitebuilder.annotation.MediumTest;
12 import android.text.Editable;
13 import android.text.Selection;
14 import android.view.KeyEvent;
15 import android.view.View;
16
17 import org.chromium.base.test.util.DisabledTest;
18 import org.chromium.base.test.util.Feature;
19 import org.chromium.base.test.util.UrlUtils;
20 import org.chromium.content.browser.RenderCoordinates;
21 import org.chromium.content.browser.test.util.Criteria;
22 import org.chromium.content.browser.test.util.CriteriaHelper;
23 import org.chromium.content.browser.test.util.DOMUtils;
24 import org.chromium.content.browser.test.util.TestInputMethodManagerWrapper;
25 import org.chromium.content.browser.test.util.TestTouchUtils;
26 import org.chromium.content.browser.test.util.TouchCommon;
27 import org.chromium.content_shell_apk.ContentShellTestBase;
28
29 /**
30  * Tests the insertion handle that allows users to paste text.
31  */
32 public class InsertionHandleTest extends ContentShellTestBase {
33     private static final String META_DISABLE_ZOOM =
34         "<meta name=\"viewport\" content=\"" +
35         "height=device-height," +
36         "width=device-width," +
37         "initial-scale=1.0," +
38         "minimum-scale=1.0," +
39         "maximum-scale=1.0," +
40         "\" />";
41
42     private static final String TEXTAREA_ID = "textarea";
43     private static final String TEXTAREA_DATA_URL = UrlUtils.encodeHtmlDataUri(
44             "<html><head>" + META_DISABLE_ZOOM + "</head><body>" +
45             "<textarea id=\"" + TEXTAREA_ID + "\" cols=\"20\" rows=\"10\">" +
46             "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor " +
47             "incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud " +
48             "exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute " +
49             "irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla " +
50             "pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui " +
51             "officia deserunt mollit anim id est laborum." +
52             "</textarea>" +
53             "</body></html>");
54
55     private static final String INPUT_TEXT_ID = "input_text";
56     private static final String INPUT_TEXT_DATA_URL = UrlUtils.encodeHtmlDataUri(
57             "<html><head>" + META_DISABLE_ZOOM + "</head><body>" +
58             "<input id=\"input_text\" type=\"text\" value=\"" +
59             "T0D0(cjhopman): put amusing sample text here. Make sure it is at least " +
60             "100 characters.  123456789012345678901234567890\" size=20></input>" +
61             "</body></html>");
62
63     // Offset to compensate for the fact that the handle is below the text.
64     private static final int VERTICAL_OFFSET = 10;
65     private static final int HANDLE_POSITION_TOLERANCE = 20;
66     private static final String PASTE_TEXT = "**test text to paste**";
67
68
69     public void launchWithUrl(String url) throws Throwable {
70         launchContentShellWithUrl(url);
71         assertTrue("Page failed to load", waitForActiveShellToBeDoneLoading());
72         assertWaitForPageScaleFactorMatch(1.0f);
73
74         // The TestInputMethodManagerWrapper intercepts showSoftInput so that a keyboard is never
75         // brought up.
76         getContentViewCore().getImeAdapterForTest().setInputMethodManagerWrapper(
77                 new TestInputMethodManagerWrapper(getContentViewCore()));
78     }
79
80     @MediumTest
81     @Feature({"TextSelection", "TextInput", "Main"})
82     public void testUnselectHidesHandle() throws Throwable {
83         launchWithUrl(TEXTAREA_DATA_URL);
84         clickNodeToShowInsertionHandle(TEXTAREA_ID);
85
86         // Unselecting should cause the handle to disappear.
87         unselectOnMainSync();
88         assertTrue(waitForHandleShowingEquals(false));
89     }
90
91
92     /**
93      * @MediumTest
94      * @Feature({"TextSelection", "TextInput", "Main"})
95      * http://crbug.com/169648
96      */
97     @DisabledTest
98     public void testKeyEventHidesHandle() throws Throwable {
99         launchWithUrl(TEXTAREA_DATA_URL);
100         clickNodeToShowInsertionHandle(TEXTAREA_ID);
101
102         getInstrumentation().sendKeySync(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_X));
103         getInstrumentation().sendKeySync(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_X));
104         assertTrue(waitForHandleShowingEquals(false));
105     }
106
107     /**
108      * @MediumTest
109      * @Feature({"TextSelection", "TextInput", "Main"})
110      * http://crbug.com/169648
111      */
112     @DisabledTest
113     public void testDragInsertionHandle() throws Throwable {
114         launchWithUrl(TEXTAREA_DATA_URL);
115
116         clickNodeToShowInsertionHandle(TEXTAREA_ID);
117
118         InsertionHandleController ihc = getContentViewCore().getInsertionHandleControllerForTest();
119         HandleView handle = ihc.getHandleViewForTest();
120
121         int initialX = handle.getPositionX();
122         int initialY = handle.getPositionY();
123         int dragToX = initialX + 120;
124         int dragToY = initialY + 120;
125
126         dragHandleTo(dragToX, dragToY);
127         assertWaitForHandleNear(dragToX, dragToY);
128     }
129
130
131     @MediumTest
132     @Feature({"TextSelection", "TextInput", "Main"})
133     public void testPasteAtInsertionHandle() throws Throwable {
134         launchWithUrl(TEXTAREA_DATA_URL);
135
136         clickNodeToShowInsertionHandle(TEXTAREA_ID);
137
138         int offset = getSelectionStart();
139         String initialText = getEditableText();
140
141         saveToClipboard(PASTE_TEXT);
142         pasteOnMainSync();
143
144         String expectedText =
145                initialText.substring(0, offset) + PASTE_TEXT + initialText.substring(offset);
146         assertTrue(waitForEditableTextEquals(expectedText));
147         assertTrue(waitForHandleShowingEquals(false));
148     }
149
150     /**
151      * @MediumTest
152      * @Feature({"TextSelection", "TextInput", "Main"})
153      * http://crbug.com/169648
154      */
155     @DisabledTest
156     public void testDragInsertionHandleInputText() throws Throwable {
157         launchWithUrl(INPUT_TEXT_DATA_URL);
158
159         clickNodeToShowInsertionHandle(INPUT_TEXT_ID);
160
161         InsertionHandleController ihc = getContentViewCore().getInsertionHandleControllerForTest();
162         HandleView handle = ihc.getHandleViewForTest();
163
164         int initialX = handle.getPositionX();
165         int initialY = handle.getPositionY();
166         int dragToX = initialX + 120;
167         int dragToY = initialY;
168         dragHandleTo(dragToX, dragToY);
169         assertWaitForHandleNear(dragToX, initialY);
170
171         TestTouchUtils.sleepForDoubleTapTimeout(getInstrumentation());
172
173         initialX = handle.getPositionX();
174         initialY = handle.getPositionY();
175         dragToY = initialY + 120;
176         dragHandleTo(initialX, dragToY);
177         // Vertical drag should not change the y-position.
178         assertWaitForHandleNear(initialX, initialY);
179     }
180
181     /**
182      * @MediumTest
183      * @Feature({"TextSelection", "TextInput", "Main"})
184      * http://crbug.com/169648
185      */
186     @DisabledTest
187     public void testDragInsertionHandleInputTextOutsideBounds() throws Throwable {
188         launchWithUrl(INPUT_TEXT_DATA_URL);
189
190         clickNodeToShowInsertionHandle(INPUT_TEXT_ID);
191
192         InsertionHandleController ihc = getContentViewCore().getInsertionHandleControllerForTest();
193         HandleView handle = ihc.getHandleViewForTest();
194
195         int initialX = handle.getPositionX();
196         int initialY = handle.getPositionY();
197         int dragToX = initialX;
198         int dragToY = initialY + 150;
199
200         // A vertical drag should not move the insertion handle.
201         dragHandleTo(dragToX, dragToY);
202         assertWaitForHandleNear(initialX, initialY);
203
204         // The input box does not go to the edge of the screen, and neither should the insertion
205         // handle.
206         dragToX = getContentViewCore().getContainerView().getWidth();
207         dragHandleTo(dragToX, dragToY);
208         assertTrue(handle.getPositionX() < dragToX - 100);
209     }
210
211     @Override
212     protected void tearDown() throws Exception {
213         super.tearDown();
214         // No way to just clear clipboard, so setting to empty string instead.
215         saveToClipboard("");
216     }
217
218     private void clickNodeToShowInsertionHandle(String nodeId) throws Throwable {
219         // On the first click the keyboard will be displayed but no insertion handles. On the second
220         // click (only if it changes the selection), the insertion handle is displayed. So that the
221         // second click changes the selection, the two clicks should be in sufficiently different
222         // locations.
223         Rect nodeBounds = DOMUtils.getNodeBounds(getContentViewCore(), nodeId);
224
225         RenderCoordinates renderCoordinates = getContentViewCore().getRenderCoordinates();
226         int offsetX = getContentViewCore().getViewportSizeOffsetWidthPix();
227         int offsetY = getContentViewCore().getViewportSizeOffsetHeightPix();
228         float left = renderCoordinates.fromLocalCssToPix(nodeBounds.left) + offsetX;
229         float right = renderCoordinates.fromLocalCssToPix(nodeBounds.right) + offsetX;
230         float top = renderCoordinates.fromLocalCssToPix(nodeBounds.top) + offsetY;
231         float bottom = renderCoordinates.fromLocalCssToPix(nodeBounds.bottom) + offsetY;
232
233         TouchCommon touchCommon = new TouchCommon(this);
234         touchCommon.singleClickView(getContentViewCore().getContainerView(),
235                 (int)(left + 3 * (right - left) / 4), (int)(top + (bottom - top) / 2));
236
237
238         TestTouchUtils.sleepForDoubleTapTimeout(getInstrumentation());
239         assertTrue(waitForHasSelectionPosition());
240
241         // TODO(cjhopman): Wait for keyboard display finished?
242         touchCommon.singleClickView(getContentViewCore().getContainerView(),
243                 (int)(left + (right - left) / 4), (int)(top + (bottom - top) / 2));
244         assertTrue(waitForHandleShowingEquals(true));
245         assertTrue(waitForHandleViewStopped());
246     }
247
248     private boolean waitForHandleViewStopped() throws Throwable {
249         // If the polling interval is too short, slowly moving may be detected as not moving.
250         final int POLLING_INTERVAL = 200;
251         return CriteriaHelper.pollForCriteria(new Criteria() {
252             int mPositionX = -1;
253             int mPositionY = -1;
254             @Override
255             public boolean isSatisfied() {
256                 int lastPositionX = mPositionX;
257                 int lastPositionY = mPositionY;
258                 InsertionHandleController ihc =
259                         getContentViewCore().getInsertionHandleControllerForTest();
260                 HandleView handle = ihc.getHandleViewForTest();
261                 mPositionX = handle.getPositionX();
262                 mPositionY = handle.getPositionY();
263                 return !handle.isDragging() &&
264                         mPositionX == lastPositionX && mPositionY == lastPositionY;
265             }
266         }, CriteriaHelper.DEFAULT_MAX_TIME_TO_POLL, POLLING_INTERVAL);
267     }
268
269     private boolean waitForEditableTextEquals(final String expectedText)
270             throws Throwable {
271         return CriteriaHelper.pollForCriteria(new Criteria() {
272             @Override
273             public boolean isSatisfied() {
274                 return getEditableText().trim().equals(expectedText.trim());
275             }
276         });
277     }
278
279     private boolean waitForHasSelectionPosition()
280             throws Throwable {
281         return CriteriaHelper.pollForCriteria(new Criteria() {
282             @Override
283             public boolean isSatisfied() {
284                 int start = getSelectionStart();
285                 int end = getSelectionEnd();
286                 return start > 0 && start == end;
287             }
288         });
289     }
290
291     private void dragHandleTo(int dragToX, int dragToY, int steps) throws Throwable {
292         InsertionHandleController ihc = getContentViewCore().getInsertionHandleControllerForTest();
293         HandleView handle = ihc.getHandleViewForTest();
294         int initialX = handle.getPositionX();
295         int initialY = handle.getPositionY();
296         View view = getContentViewCore().getContainerView();
297
298         int fromLocation[] = TestTouchUtils.getAbsoluteLocationFromRelative(view, initialX,
299                 initialY + VERTICAL_OFFSET);
300         int toLocation[] = TestTouchUtils.getAbsoluteLocationFromRelative(view, dragToX,
301                 dragToY + VERTICAL_OFFSET);
302
303         long downTime = TestTouchUtils.dragStart(getInstrumentation(), fromLocation[0],
304                 fromLocation[1]);
305         assertWaitForHandleDraggingEquals(true);
306         TestTouchUtils.dragTo(getInstrumentation(), fromLocation[0], toLocation[0],
307                 fromLocation[1], toLocation[1], steps, downTime);
308         TestTouchUtils.dragEnd(getInstrumentation(), toLocation[0], toLocation[1], downTime);
309         assertWaitForHandleDraggingEquals(false);
310         assertTrue(waitForHandleViewStopped());
311     }
312
313     private void dragHandleTo(int dragToX, int dragToY) throws Throwable {
314         dragHandleTo(dragToX, dragToY, 5);
315     }
316
317     private void assertWaitForHandleDraggingEquals(final boolean expected) throws Throwable {
318         InsertionHandleController ihc = getContentViewCore().getInsertionHandleControllerForTest();
319         final HandleView handle = ihc.getHandleViewForTest();
320         assertTrue(CriteriaHelper.pollForCriteria(new Criteria() {
321             @Override
322             public boolean isSatisfied() {
323                 return handle.isDragging() == expected;
324             }
325         }));
326     }
327
328     private static boolean isHandleNear(HandleView handle, int x, int y) {
329         return (Math.abs(handle.getPositionX() - x) < HANDLE_POSITION_TOLERANCE) &&
330                 (Math.abs(handle.getPositionY() - VERTICAL_OFFSET - y) < HANDLE_POSITION_TOLERANCE);
331     }
332
333     private void assertWaitForHandleNear(final int x, final int y) throws Throwable {
334         InsertionHandleController ihc = getContentViewCore().getInsertionHandleControllerForTest();
335         final HandleView handle = ihc.getHandleViewForTest();
336         assertTrue(CriteriaHelper.pollForCriteria(new Criteria() {
337             @Override
338             public boolean isSatisfied() {
339                 return isHandleNear(handle, x, y);
340             }
341         }));
342     }
343
344     private boolean waitForHandleShowingEquals(final boolean shouldBeShowing)
345             throws Throwable {
346         return CriteriaHelper.pollForCriteria(new Criteria() {
347             @Override
348             public boolean isSatisfied() {
349                 InsertionHandleController ihc =
350                         getContentViewCore().getInsertionHandleControllerForTest();
351                 boolean isShowing = ihc != null && ihc.isShowing();
352                 return isShowing == shouldBeShowing;
353             }
354         });
355     }
356
357     private void pasteOnMainSync() {
358         getInstrumentation().runOnMainSync(new Runnable() {
359             @Override
360             public void run() {
361                 getContentViewCore().getInsertionHandleControllerForTest().paste();
362             }
363         });
364     }
365
366     private void unselectOnMainSync() {
367         getInstrumentation().runOnMainSync(new Runnable() {
368             @Override
369             public void run() {
370                 getContentViewCore().getImeAdapterForTest().unselect();
371             }
372         });
373     }
374
375     private int getSelectionStart() {
376         return Selection.getSelectionStart(getEditable());
377     }
378
379     private int getSelectionEnd() {
380         return Selection.getSelectionEnd(getEditable());
381     }
382
383     private Editable getEditable() {
384         return getContentViewCore().getEditableForTest();
385     }
386
387     private String getEditableText() {
388         return getEditable().toString();
389     }
390
391     private void saveToClipboard(String text) {
392         ClipboardManager clipMgr =
393                 (ClipboardManager) getActivity().getSystemService(Context.CLIPBOARD_SERVICE);
394         clipMgr.setPrimaryClip(ClipData.newPlainText(null, text));
395     }
396 }