Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(690)

Side by Side Diff: content/public/android/javatests/src/org/chromium/content/browser/input/ImeActivityTestRule.java

Issue 2766393004: Convert most of the rest of instrumentation tests in content (Closed)
Patch Set: rebase Created 3 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2017 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.annotation.TargetApi;
8 import android.app.Activity;
9 import android.content.ClipData;
10 import android.content.ClipboardManager;
11 import android.content.Context;
12 import android.os.Handler;
13 import android.text.TextUtils;
14 import android.util.Pair;
15 import android.view.KeyEvent;
16 import android.view.View;
17 import android.view.inputmethod.EditorInfo;
18 import android.view.inputmethod.InputConnection;
19
20 import org.junit.Assert;
21
22 import org.chromium.base.ThreadUtils;
23 import org.chromium.content.browser.ContentViewCore;
24 import org.chromium.content.browser.SelectionPopupController;
25 import org.chromium.content.browser.test.util.Criteria;
26 import org.chromium.content.browser.test.util.CriteriaHelper;
27 import org.chromium.content.browser.test.util.DOMUtils;
28 import org.chromium.content.browser.test.util.JavaScriptUtils;
29 import org.chromium.content.browser.test.util.TestCallbackHelperContainer;
30 import org.chromium.content.browser.test.util.TestInputMethodManagerWrapper;
31 import org.chromium.content_public.browser.WebContents;
32 import org.chromium.content_shell_apk.ContentShellActivityTestRule;
33 import org.chromium.ui.base.ime.TextInputType;
34
35 import java.util.ArrayList;
36 import java.util.Arrays;
37 import java.util.List;
38 import java.util.concurrent.Callable;
39 import java.util.concurrent.ExecutionException;
40 import java.util.concurrent.TimeoutException;
41
42 /**
43 * Integration tests for text input for Android L (or above) features.
44 */
45 class ImeActivityTestRule extends ContentShellActivityTestRule {
46 private ChromiumBaseInputConnection mConnection;
47 private TestInputConnectionFactory mConnectionFactory;
48 private ImeAdapter mImeAdapter;
49
50 static final String INPUT_FORM_HTML = "content/test/data/android/input/input _forms.html";
51
52 private ContentViewCore mContentViewCore;
53 private SelectionPopupController mSelectionPopupController;
54 private TestCallbackHelperContainer mCallbackContainer;
55 private TestInputMethodManagerWrapper mInputMethodManagerWrapper;
56
57 public void setUp() throws Exception {
58 launchContentShellWithUrlSync(INPUT_FORM_HTML);
59 mContentViewCore = getContentViewCore();
60 mSelectionPopupController = mContentViewCore.getSelectionPopupController ForTesting();
61 mInputMethodManagerWrapper = new TestInputMethodManagerWrapper(mContentV iewCore) {
62 private boolean mExpectsSelectionOutsideComposition;
63
64 @Override
65 public void expectsSelectionOutsideComposition() {
66 mExpectsSelectionOutsideComposition = true;
67 }
68
69 @Override
70 public void onUpdateSelection(
71 Range oldSel, Range oldComp, Range newSel, Range newComp) {
72 // We expect that selection will be outside composition in some cases. Keyboard
73 // app will not finish composition in this case.
74 if (mExpectsSelectionOutsideComposition) {
75 mExpectsSelectionOutsideComposition = false;
76 return;
77 }
78 if (oldComp == null || oldComp.start() == oldComp.end()
79 || newComp.start() == newComp.end()) {
80 return;
81 }
82 // This emulates keyboard app's behavior that finishes compositi on when
83 // selection is outside composition.
84 if (!newSel.intersects(newComp)) {
85 try {
86 finishComposingText();
87 } catch (Exception e) {
88 e.printStackTrace();
89 Assert.fail();
90 }
91 }
92 }
93 };
94 getImeAdapter().setInputMethodManagerWrapperForTest(mInputMethodManagerW rapper);
95 Assert.assertEquals(0, mInputMethodManagerWrapper.getShowSoftInputCounte r());
96 mConnectionFactory =
97 new TestInputConnectionFactory(getImeAdapter().getInputConnectio nFactoryForTest());
98 getImeAdapter().setInputConnectionFactory(mConnectionFactory);
99
100 mCallbackContainer = new TestCallbackHelperContainer(mContentViewCore);
101 DOMUtils.waitForNonZeroNodeBounds(getWebContents(), "input_text");
102 boolean result = DOMUtils.clickNode(mContentViewCore, "input_text");
103
104 Assert.assertEquals("Failed to dispatch touch event.", true, result);
105 assertWaitForKeyboardStatus(true);
106
107 mConnection = getInputConnection();
108 mImeAdapter = getImeAdapter();
109
110 waitForKeyboardStates(1, 0, 1, new Integer[] {TextInputType.TEXT});
111 Assert.assertEquals(0, mConnectionFactory.getOutAttrs().initialSelStart) ;
112 Assert.assertEquals(0, mConnectionFactory.getOutAttrs().initialSelEnd);
113
114 waitForEventLogs("selectionchange");
115 clearEventLogs();
116
117 waitAndVerifyUpdateSelection(0, 0, 0, -1, -1);
118 resetAllStates();
119 }
120
121 SelectionPopupController getSelectionPopupController() {
122 return mSelectionPopupController;
123 }
124
125 TestCallbackHelperContainer getTestCallBackHelperContainer() {
126 return mCallbackContainer;
127 }
128
129 ChromiumBaseInputConnection getConnection() {
130 return mConnection;
131 }
132
133 TestInputMethodManagerWrapper getInputMethodManagerWrapper() {
134 return mInputMethodManagerWrapper;
135 }
136
137 TestInputConnectionFactory getConnectionFactory() {
138 return mConnectionFactory;
139 }
140
141 void fullyLoadUrl(final String url) throws Throwable {
142 ThreadUtils.runOnUiThreadBlocking(new Runnable() {
143 @Override
144 public void run() {
145 getActivity().getActiveShell().loadUrl(url);
146 }
147 });
148 waitForActiveShellToBeDoneLoading();
149 }
150
151 void clearEventLogs() throws Exception {
152 final String code = "clearEventLogs()";
153 JavaScriptUtils.executeJavaScriptAndWaitForResult(
154 getContentViewCore().getWebContents(), code);
155 }
156
157 void waitForEventLogs(String expectedLogs) throws Exception {
158 final String code = "getEventLogs()";
159 final String sanitizedExpectedLogs = "\"" + expectedLogs + "\"";
160 Assert.assertEquals(sanitizedExpectedLogs,
161 JavaScriptUtils.executeJavaScriptAndWaitForResult(
162 getContentViewCore().getWebContents(), code));
163 }
164
165 void assertTextsAroundCursor(CharSequence before, CharSequence selected, Cha rSequence after)
166 throws Exception {
167 Assert.assertEquals(before, getTextBeforeCursor(100, 0));
168 Assert.assertEquals(selected, getSelectedText(0));
169 Assert.assertEquals(after, getTextAfterCursor(100, 0));
170 }
171
172 void waitForKeyboardStates(int show, int hide, int restart, Integer[] histor y) {
173 final String expected = stringifyKeyboardStates(show, hide, restart, his tory);
174 CriteriaHelper.pollUiThread(Criteria.equals(expected, new Callable<Strin g>() {
175 @Override
176 public String call() {
177 return getKeyboardStates();
178 }
179 }));
180 }
181
182 void resetAllStates() {
183 mInputMethodManagerWrapper.reset();
184 mConnectionFactory.clearTextInputTypeHistory();
185 }
186
187 String getKeyboardStates() {
188 int showCount = mInputMethodManagerWrapper.getShowSoftInputCounter();
189 int hideCount = mInputMethodManagerWrapper.getHideSoftInputCounter();
190 int restartCount = mInputMethodManagerWrapper.getRestartInputCounter();
191 Integer[] history = mConnectionFactory.getTextInputTypeHistory();
192 return stringifyKeyboardStates(showCount, hideCount, restartCount, histo ry);
193 }
194
195 String stringifyKeyboardStates(int show, int hide, int restart, Integer[] hi story) {
196 return "show count: " + show + ", hide count: " + hide + ", restart coun t: " + restart
197 + ", input type history: " + Arrays.deepToString(history);
198 }
199
200 void performGo(TestCallbackHelperContainer testCallbackHelperContainer) thro ws Throwable {
201 final InputConnection inputConnection = mConnection;
202 final Callable<Void> callable = new Callable<Void>() {
203 @Override
204 public Void call() throws Exception {
205 inputConnection.performEditorAction(EditorInfo.IME_ACTION_GO);
206 return null;
207 }
208 };
209
210 handleBlockingCallbackAction(
211 testCallbackHelperContainer.getOnPageFinishedHelper(), new Runna ble() {
212 @Override
213 public void run() {
214 try {
215 runBlockingOnImeThread(callable);
216 } catch (Exception e) {
217 e.printStackTrace();
218 Assert.fail();
219 }
220 }
221 });
222 }
223
224 void assertWaitForKeyboardStatus(final boolean show) {
225 CriteriaHelper.pollUiThread(new Criteria() {
226 @Override
227 public boolean isSatisfied() {
228 // We do not check the other way around: in some cases we need t o keep
229 // input connection even when the last known status is 'hidden'.
230 if (show && getInputConnection() == null) {
231 updateFailureReason("input connection should not be null.");
232 return false;
233 }
234 updateFailureReason("expected show: " + show);
235 return show == mInputMethodManagerWrapper.isShowWithoutHideOutst anding();
236 }
237 });
238 }
239
240 void assertWaitForSelectActionBarStatus(final boolean show) {
241 CriteriaHelper.pollUiThread(Criteria.equals(show, new Callable<Boolean>( ) {
242 @Override
243 public Boolean call() {
244 return mContentViewCore.isSelectActionBarShowing();
245 }
246 }));
247 }
248
249 void waitAndVerifyUpdateSelection(final int index, final int selectionStart,
250 final int selectionEnd, final int compositionStart, final int compos itionEnd) {
251 final List<Pair<Range, Range>> states = mInputMethodManagerWrapper.getUp dateSelectionList();
252 CriteriaHelper.pollUiThread(new Criteria() {
253 @Override
254 public boolean isSatisfied() {
255 return states.size() > index;
256 }
257 });
258 Pair<Range, Range> selection = states.get(index);
259 Assert.assertEquals(selectionStart, selection.first.start());
260 Assert.assertEquals(selectionEnd, selection.first.end());
261 Assert.assertEquals(compositionStart, selection.second.start());
262 Assert.assertEquals(compositionEnd, selection.second.end());
263 }
264
265 void resetUpdateSelectionList() {
266 mInputMethodManagerWrapper.getUpdateSelectionList().clear();
267 }
268
269 void assertClipboardContents(final Activity activity, final String expectedC ontents) {
270 CriteriaHelper.pollUiThread(new Criteria() {
271 @Override
272 public boolean isSatisfied() {
273 ClipboardManager clipboardManager =
274 (ClipboardManager) activity.getSystemService(Context.CLI PBOARD_SERVICE);
275 ClipData clip = clipboardManager.getPrimaryClip();
276 return clip != null && clip.getItemCount() == 1
277 && TextUtils.equals(clip.getItemAt(0).getText(), expecte dContents);
278 }
279 });
280 }
281
282 ImeAdapter getImeAdapter() {
283 return mContentViewCore.getImeAdapterForTest();
284 }
285
286 ChromiumBaseInputConnection getInputConnection() {
287 try {
288 return ThreadUtils.runOnUiThreadBlocking(new Callable<ChromiumBaseIn putConnection>() {
289 @Override
290 public ChromiumBaseInputConnection call() {
291 return mContentViewCore.getImeAdapterForTest().getInputConne ctionForTest();
292 }
293 });
294 } catch (ExecutionException e) {
295 e.printStackTrace();
296 Assert.fail();
297 return null;
298 }
299 }
300
301 void restartInput() {
302 ThreadUtils.runOnUiThreadBlocking(new Runnable() {
303 @Override
304 public void run() {
305 mImeAdapter.restartInput();
306 }
307 });
308 }
309
310 // After calling this method, we should call assertClipboardContents() to wa it for the clipboard
311 // to get updated. See cubug.com/621046
312 void copy() {
313 final WebContents webContents = getWebContents();
314 ThreadUtils.runOnUiThreadBlocking(new Runnable() {
315 @Override
316 public void run() {
317 webContents.copy();
318 }
319 });
320 }
321
322 void cut() {
323 final WebContents webContents = getWebContents();
324 ThreadUtils.runOnUiThreadBlocking(new Runnable() {
325 @Override
326 public void run() {
327 webContents.cut();
328 }
329 });
330 }
331
332 void setClip(final CharSequence text) {
333 ThreadUtils.runOnUiThreadBlocking(new Runnable() {
334 @Override
335 public void run() {
336 final ClipboardManager clipboardManager =
337 (ClipboardManager) getActivity().getSystemService(
338 Context.CLIPBOARD_SERVICE);
339 clipboardManager.setPrimaryClip(ClipData.newPlainText(null, text ));
340 }
341 });
342 }
343
344 void paste() {
345 final WebContents webContents = getWebContents();
346 ThreadUtils.runOnUiThreadBlocking(new Runnable() {
347 @Override
348 public void run() {
349 webContents.paste();
350 }
351 });
352 }
353
354 void selectAll() {
355 final WebContents webContents = getWebContents();
356 ThreadUtils.runOnUiThreadBlocking(new Runnable() {
357 @Override
358 public void run() {
359 webContents.selectAll();
360 }
361 });
362 }
363
364 void collapseSelection() {
365 final WebContents webContents = getWebContents();
366 ThreadUtils.runOnUiThreadBlocking(new Runnable() {
367 @Override
368 public void run() {
369 webContents.collapseSelection();
370 }
371 });
372 }
373
374 /**
375 * Run the {@Callable} on IME thread (or UI thread if not applicable).
376 * @param c The callable
377 * @return The result from running the callable.
378 */
379 <T> T runBlockingOnImeThread(Callable<T> c) throws Exception {
380 return ImeTestUtils.runBlockingOnHandler(mConnectionFactory.getHandler() , c);
381 }
382
383 boolean beginBatchEdit() throws Exception {
384 final ChromiumBaseInputConnection connection = mConnection;
385 return runBlockingOnImeThread(new Callable<Boolean>() {
386 @Override
387 public Boolean call() {
388 return connection.beginBatchEdit();
389 }
390 });
391 }
392
393 boolean endBatchEdit() throws Exception {
394 final ChromiumBaseInputConnection connection = mConnection;
395 return runBlockingOnImeThread(new Callable<Boolean>() {
396 @Override
397 public Boolean call() {
398 return connection.endBatchEdit();
399 }
400 });
401 }
402
403 boolean commitText(final CharSequence text, final int newCursorPosition) thr ows Exception {
404 final ChromiumBaseInputConnection connection = mConnection;
405 return runBlockingOnImeThread(new Callable<Boolean>() {
406 @Override
407 public Boolean call() {
408 return connection.commitText(text, newCursorPosition);
409 }
410 });
411 }
412
413 boolean setSelection(final int start, final int end) throws Exception {
414 final ChromiumBaseInputConnection connection = mConnection;
415 return runBlockingOnImeThread(new Callable<Boolean>() {
416 @Override
417 public Boolean call() {
418 return connection.setSelection(start, end);
419 }
420 });
421 }
422
423 boolean setComposingRegion(final int start, final int end) throws Exception {
424 final ChromiumBaseInputConnection connection = mConnection;
425 return runBlockingOnImeThread(new Callable<Boolean>() {
426 @Override
427 public Boolean call() {
428 return connection.setComposingRegion(start, end);
429 }
430 });
431 }
432
433 protected boolean setComposingText(final CharSequence text, final int newCur sorPosition)
434 throws Exception {
435 final ChromiumBaseInputConnection connection = mConnection;
436 return runBlockingOnImeThread(new Callable<Boolean>() {
437 @Override
438 public Boolean call() {
439 return connection.setComposingText(text, newCursorPosition);
440 }
441 });
442 }
443
444 boolean finishComposingText() throws Exception {
445 final ChromiumBaseInputConnection connection = mConnection;
446 return runBlockingOnImeThread(new Callable<Boolean>() {
447 @Override
448 public Boolean call() {
449 return connection.finishComposingText();
450 }
451 });
452 }
453
454 boolean deleteSurroundingText(final int before, final int after) throws Exce ption {
455 final ChromiumBaseInputConnection connection = mConnection;
456 return runBlockingOnImeThread(new Callable<Boolean>() {
457 @Override
458 public Boolean call() {
459 return connection.deleteSurroundingText(before, after);
460 }
461 });
462 }
463
464 // Note that deleteSurroundingTextInCodePoints() was introduced in Android N (Api level 24), but
465 // the Android repository used in Chrome is behind that (level 23). So this function can't be
466 // called by keyboard apps currently.
467 @TargetApi(24)
468 boolean deleteSurroundingTextInCodePoints(final int before, final int after) throws Exception {
469 final ThreadedInputConnection connection = (ThreadedInputConnection) mCo nnection;
470 return runBlockingOnImeThread(new Callable<Boolean>() {
471 @Override
472 public Boolean call() {
473 return connection.deleteSurroundingTextInCodePoints(before, afte r);
474 }
475 });
476 }
477
478 CharSequence getTextBeforeCursor(final int length, final int flags) throws E xception {
479 final ChromiumBaseInputConnection connection = mConnection;
480 return runBlockingOnImeThread(new Callable<CharSequence>() {
481 @Override
482 public CharSequence call() {
483 return connection.getTextBeforeCursor(length, flags);
484 }
485 });
486 }
487
488 CharSequence getSelectedText(final int flags) throws Exception {
489 final ChromiumBaseInputConnection connection = mConnection;
490 return runBlockingOnImeThread(new Callable<CharSequence>() {
491 @Override
492 public CharSequence call() {
493 return connection.getSelectedText(flags);
494 }
495 });
496 }
497
498 CharSequence getTextAfterCursor(final int length, final int flags) throws Ex ception {
499 final ChromiumBaseInputConnection connection = mConnection;
500 return runBlockingOnImeThread(new Callable<CharSequence>() {
501 @Override
502 public CharSequence call() {
503 return connection.getTextAfterCursor(length, flags);
504 }
505 });
506 }
507
508 int getCursorCapsMode(final int reqModes) throws Throwable {
509 final ChromiumBaseInputConnection connection = mConnection;
510 return runBlockingOnImeThread(new Callable<Integer>() {
511 @Override
512 public Integer call() {
513 return connection.getCursorCapsMode(reqModes);
514 }
515 });
516 }
517
518 void dispatchKeyEvent(final KeyEvent event) {
519 ThreadUtils.runOnUiThreadBlocking(new Runnable() {
520 @Override
521 public void run() {
522 mImeAdapter.dispatchKeyEvent(event);
523 }
524 });
525 }
526
527 /**
528 * Focus element, wait for a single state update, reset state update list.
529 * @param id ID of the element to focus.
530 */
531 void focusElementAndWaitForStateUpdate(String id)
532 throws InterruptedException, TimeoutException {
533 resetUpdateSelectionList();
534 focusElement(id);
535 waitAndVerifyUpdateSelection(0, 0, 0, -1, -1);
536 resetUpdateSelectionList();
537 }
538
539 void focusElement(final String id) throws InterruptedException, TimeoutExcep tion {
540 focusElement(id, true);
541 }
542
543 void focusElement(final String id, boolean shouldShowKeyboard)
544 throws InterruptedException, TimeoutException {
545 DOMUtils.focusNode(getWebContents(), id);
546 assertWaitForKeyboardStatus(shouldShowKeyboard);
547 CriteriaHelper.pollInstrumentationThread(Criteria.equals(id, new Callabl e<String>() {
548 @Override
549 public String call() throws Exception {
550 return DOMUtils.getFocusedNode(getWebContents());
551 }
552 }));
553 // When we focus another element, the connection may be recreated.
554 mConnection = getInputConnection();
555 }
556
557 static class TestInputConnectionFactory implements ChromiumBaseInputConnecti on.Factory {
558 private final ChromiumBaseInputConnection.Factory mFactory;
559
560 private final List<Integer> mTextInputTypeList = new ArrayList<>();
561 private EditorInfo mOutAttrs;
562
563 public TestInputConnectionFactory(ChromiumBaseInputConnection.Factory fa ctory) {
564 mFactory = factory;
565 }
566
567 @Override
568 public ChromiumBaseInputConnection initializeAndGet(View view, ImeAdapte r imeAdapter,
569 int inputType, int inputFlags, int inputMode, int selectionStart , int selectionEnd,
570 EditorInfo outAttrs) {
571 mTextInputTypeList.add(inputType);
572 mOutAttrs = outAttrs;
573 return mFactory.initializeAndGet(view, imeAdapter, inputType, inputM ode, inputFlags,
574 selectionStart, selectionEnd, outAttrs);
575 }
576
577 @Override
578 public Handler getHandler() {
579 return mFactory.getHandler();
580 }
581
582 public Integer[] getTextInputTypeHistory() {
583 Integer[] result = new Integer[mTextInputTypeList.size()];
584 mTextInputTypeList.toArray(result);
585 return result;
586 }
587
588 public void clearTextInputTypeHistory() {
589 mTextInputTypeList.clear();
590 }
591
592 public EditorInfo getOutAttrs() {
593 return mOutAttrs;
594 }
595
596 @Override
597 public void onWindowFocusChanged(boolean gainFocus) {
598 mFactory.onWindowFocusChanged(gainFocus);
599 }
600
601 @Override
602 public void onViewFocusChanged(boolean gainFocus) {
603 mFactory.onViewFocusChanged(gainFocus);
604 }
605
606 @Override
607 public void onViewAttachedToWindow() {
608 mFactory.onViewAttachedToWindow();
609 }
610
611 @Override
612 public void onViewDetachedFromWindow() {
613 mFactory.onViewDetachedFromWindow();
614 }
615 }
616 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698