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