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

Unified 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 side-by-side diff with in-line comments
Download patch
Index: content/public/android/javatests/src/org/chromium/content/browser/input/ImeActivityTestRule.java
diff --git a/content/public/android/javatests/src/org/chromium/content/browser/input/ImeActivityTestRule.java b/content/public/android/javatests/src/org/chromium/content/browser/input/ImeActivityTestRule.java
new file mode 100644
index 0000000000000000000000000000000000000000..8ec3f26ae7f5d27085c2e60c2afce103d4181d34
--- /dev/null
+++ b/content/public/android/javatests/src/org/chromium/content/browser/input/ImeActivityTestRule.java
@@ -0,0 +1,616 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.content.browser.input;
+
+import android.annotation.TargetApi;
+import android.app.Activity;
+import android.content.ClipData;
+import android.content.ClipboardManager;
+import android.content.Context;
+import android.os.Handler;
+import android.text.TextUtils;
+import android.util.Pair;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputConnection;
+
+import org.junit.Assert;
+
+import org.chromium.base.ThreadUtils;
+import org.chromium.content.browser.ContentViewCore;
+import org.chromium.content.browser.SelectionPopupController;
+import org.chromium.content.browser.test.util.Criteria;
+import org.chromium.content.browser.test.util.CriteriaHelper;
+import org.chromium.content.browser.test.util.DOMUtils;
+import org.chromium.content.browser.test.util.JavaScriptUtils;
+import org.chromium.content.browser.test.util.TestCallbackHelperContainer;
+import org.chromium.content.browser.test.util.TestInputMethodManagerWrapper;
+import org.chromium.content_public.browser.WebContents;
+import org.chromium.content_shell_apk.ContentShellActivityTestRule;
+import org.chromium.ui.base.ime.TextInputType;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * Integration tests for text input for Android L (or above) features.
+ */
+class ImeActivityTestRule extends ContentShellActivityTestRule {
+ private ChromiumBaseInputConnection mConnection;
+ private TestInputConnectionFactory mConnectionFactory;
+ private ImeAdapter mImeAdapter;
+
+ static final String INPUT_FORM_HTML = "content/test/data/android/input/input_forms.html";
+
+ private ContentViewCore mContentViewCore;
+ private SelectionPopupController mSelectionPopupController;
+ private TestCallbackHelperContainer mCallbackContainer;
+ private TestInputMethodManagerWrapper mInputMethodManagerWrapper;
+
+ public void setUp() throws Exception {
+ launchContentShellWithUrlSync(INPUT_FORM_HTML);
+ mContentViewCore = getContentViewCore();
+ mSelectionPopupController = mContentViewCore.getSelectionPopupControllerForTesting();
+ mInputMethodManagerWrapper = new TestInputMethodManagerWrapper(mContentViewCore) {
+ private boolean mExpectsSelectionOutsideComposition;
+
+ @Override
+ public void expectsSelectionOutsideComposition() {
+ mExpectsSelectionOutsideComposition = true;
+ }
+
+ @Override
+ public void onUpdateSelection(
+ Range oldSel, Range oldComp, Range newSel, Range newComp) {
+ // We expect that selection will be outside composition in some cases. Keyboard
+ // app will not finish composition in this case.
+ if (mExpectsSelectionOutsideComposition) {
+ mExpectsSelectionOutsideComposition = false;
+ return;
+ }
+ if (oldComp == null || oldComp.start() == oldComp.end()
+ || newComp.start() == newComp.end()) {
+ return;
+ }
+ // This emulates keyboard app's behavior that finishes composition when
+ // selection is outside composition.
+ if (!newSel.intersects(newComp)) {
+ try {
+ finishComposingText();
+ } catch (Exception e) {
+ e.printStackTrace();
+ Assert.fail();
+ }
+ }
+ }
+ };
+ getImeAdapter().setInputMethodManagerWrapperForTest(mInputMethodManagerWrapper);
+ Assert.assertEquals(0, mInputMethodManagerWrapper.getShowSoftInputCounter());
+ mConnectionFactory =
+ new TestInputConnectionFactory(getImeAdapter().getInputConnectionFactoryForTest());
+ getImeAdapter().setInputConnectionFactory(mConnectionFactory);
+
+ mCallbackContainer = new TestCallbackHelperContainer(mContentViewCore);
+ DOMUtils.waitForNonZeroNodeBounds(getWebContents(), "input_text");
+ boolean result = DOMUtils.clickNode(mContentViewCore, "input_text");
+
+ Assert.assertEquals("Failed to dispatch touch event.", true, result);
+ assertWaitForKeyboardStatus(true);
+
+ mConnection = getInputConnection();
+ mImeAdapter = getImeAdapter();
+
+ waitForKeyboardStates(1, 0, 1, new Integer[] {TextInputType.TEXT});
+ Assert.assertEquals(0, mConnectionFactory.getOutAttrs().initialSelStart);
+ Assert.assertEquals(0, mConnectionFactory.getOutAttrs().initialSelEnd);
+
+ waitForEventLogs("selectionchange");
+ clearEventLogs();
+
+ waitAndVerifyUpdateSelection(0, 0, 0, -1, -1);
+ resetAllStates();
+ }
+
+ SelectionPopupController getSelectionPopupController() {
+ return mSelectionPopupController;
+ }
+
+ TestCallbackHelperContainer getTestCallBackHelperContainer() {
+ return mCallbackContainer;
+ }
+
+ ChromiumBaseInputConnection getConnection() {
+ return mConnection;
+ }
+
+ TestInputMethodManagerWrapper getInputMethodManagerWrapper() {
+ return mInputMethodManagerWrapper;
+ }
+
+ TestInputConnectionFactory getConnectionFactory() {
+ return mConnectionFactory;
+ }
+
+ void fullyLoadUrl(final String url) throws Throwable {
+ ThreadUtils.runOnUiThreadBlocking(new Runnable() {
+ @Override
+ public void run() {
+ getActivity().getActiveShell().loadUrl(url);
+ }
+ });
+ waitForActiveShellToBeDoneLoading();
+ }
+
+ void clearEventLogs() throws Exception {
+ final String code = "clearEventLogs()";
+ JavaScriptUtils.executeJavaScriptAndWaitForResult(
+ getContentViewCore().getWebContents(), code);
+ }
+
+ void waitForEventLogs(String expectedLogs) throws Exception {
+ final String code = "getEventLogs()";
+ final String sanitizedExpectedLogs = "\"" + expectedLogs + "\"";
+ Assert.assertEquals(sanitizedExpectedLogs,
+ JavaScriptUtils.executeJavaScriptAndWaitForResult(
+ getContentViewCore().getWebContents(), code));
+ }
+
+ void assertTextsAroundCursor(CharSequence before, CharSequence selected, CharSequence after)
+ throws Exception {
+ Assert.assertEquals(before, getTextBeforeCursor(100, 0));
+ Assert.assertEquals(selected, getSelectedText(0));
+ Assert.assertEquals(after, getTextAfterCursor(100, 0));
+ }
+
+ void waitForKeyboardStates(int show, int hide, int restart, Integer[] history) {
+ final String expected = stringifyKeyboardStates(show, hide, restart, history);
+ CriteriaHelper.pollUiThread(Criteria.equals(expected, new Callable<String>() {
+ @Override
+ public String call() {
+ return getKeyboardStates();
+ }
+ }));
+ }
+
+ void resetAllStates() {
+ mInputMethodManagerWrapper.reset();
+ mConnectionFactory.clearTextInputTypeHistory();
+ }
+
+ String getKeyboardStates() {
+ int showCount = mInputMethodManagerWrapper.getShowSoftInputCounter();
+ int hideCount = mInputMethodManagerWrapper.getHideSoftInputCounter();
+ int restartCount = mInputMethodManagerWrapper.getRestartInputCounter();
+ Integer[] history = mConnectionFactory.getTextInputTypeHistory();
+ return stringifyKeyboardStates(showCount, hideCount, restartCount, history);
+ }
+
+ String stringifyKeyboardStates(int show, int hide, int restart, Integer[] history) {
+ return "show count: " + show + ", hide count: " + hide + ", restart count: " + restart
+ + ", input type history: " + Arrays.deepToString(history);
+ }
+
+ void performGo(TestCallbackHelperContainer testCallbackHelperContainer) throws Throwable {
+ final InputConnection inputConnection = mConnection;
+ final Callable<Void> callable = new Callable<Void>() {
+ @Override
+ public Void call() throws Exception {
+ inputConnection.performEditorAction(EditorInfo.IME_ACTION_GO);
+ return null;
+ }
+ };
+
+ handleBlockingCallbackAction(
+ testCallbackHelperContainer.getOnPageFinishedHelper(), new Runnable() {
+ @Override
+ public void run() {
+ try {
+ runBlockingOnImeThread(callable);
+ } catch (Exception e) {
+ e.printStackTrace();
+ Assert.fail();
+ }
+ }
+ });
+ }
+
+ void assertWaitForKeyboardStatus(final boolean show) {
+ CriteriaHelper.pollUiThread(new Criteria() {
+ @Override
+ public boolean isSatisfied() {
+ // We do not check the other way around: in some cases we need to keep
+ // input connection even when the last known status is 'hidden'.
+ if (show && getInputConnection() == null) {
+ updateFailureReason("input connection should not be null.");
+ return false;
+ }
+ updateFailureReason("expected show: " + show);
+ return show == mInputMethodManagerWrapper.isShowWithoutHideOutstanding();
+ }
+ });
+ }
+
+ void assertWaitForSelectActionBarStatus(final boolean show) {
+ CriteriaHelper.pollUiThread(Criteria.equals(show, new Callable<Boolean>() {
+ @Override
+ public Boolean call() {
+ return mContentViewCore.isSelectActionBarShowing();
+ }
+ }));
+ }
+
+ void waitAndVerifyUpdateSelection(final int index, final int selectionStart,
+ final int selectionEnd, final int compositionStart, final int compositionEnd) {
+ final List<Pair<Range, Range>> states = mInputMethodManagerWrapper.getUpdateSelectionList();
+ CriteriaHelper.pollUiThread(new Criteria() {
+ @Override
+ public boolean isSatisfied() {
+ return states.size() > index;
+ }
+ });
+ Pair<Range, Range> selection = states.get(index);
+ Assert.assertEquals(selectionStart, selection.first.start());
+ Assert.assertEquals(selectionEnd, selection.first.end());
+ Assert.assertEquals(compositionStart, selection.second.start());
+ Assert.assertEquals(compositionEnd, selection.second.end());
+ }
+
+ void resetUpdateSelectionList() {
+ mInputMethodManagerWrapper.getUpdateSelectionList().clear();
+ }
+
+ void assertClipboardContents(final Activity activity, final String expectedContents) {
+ CriteriaHelper.pollUiThread(new Criteria() {
+ @Override
+ public boolean isSatisfied() {
+ ClipboardManager clipboardManager =
+ (ClipboardManager) activity.getSystemService(Context.CLIPBOARD_SERVICE);
+ ClipData clip = clipboardManager.getPrimaryClip();
+ return clip != null && clip.getItemCount() == 1
+ && TextUtils.equals(clip.getItemAt(0).getText(), expectedContents);
+ }
+ });
+ }
+
+ ImeAdapter getImeAdapter() {
+ return mContentViewCore.getImeAdapterForTest();
+ }
+
+ ChromiumBaseInputConnection getInputConnection() {
+ try {
+ return ThreadUtils.runOnUiThreadBlocking(new Callable<ChromiumBaseInputConnection>() {
+ @Override
+ public ChromiumBaseInputConnection call() {
+ return mContentViewCore.getImeAdapterForTest().getInputConnectionForTest();
+ }
+ });
+ } catch (ExecutionException e) {
+ e.printStackTrace();
+ Assert.fail();
+ return null;
+ }
+ }
+
+ void restartInput() {
+ ThreadUtils.runOnUiThreadBlocking(new Runnable() {
+ @Override
+ public void run() {
+ mImeAdapter.restartInput();
+ }
+ });
+ }
+
+ // After calling this method, we should call assertClipboardContents() to wait for the clipboard
+ // to get updated. See cubug.com/621046
+ void copy() {
+ final WebContents webContents = getWebContents();
+ ThreadUtils.runOnUiThreadBlocking(new Runnable() {
+ @Override
+ public void run() {
+ webContents.copy();
+ }
+ });
+ }
+
+ void cut() {
+ final WebContents webContents = getWebContents();
+ ThreadUtils.runOnUiThreadBlocking(new Runnable() {
+ @Override
+ public void run() {
+ webContents.cut();
+ }
+ });
+ }
+
+ void setClip(final CharSequence text) {
+ ThreadUtils.runOnUiThreadBlocking(new Runnable() {
+ @Override
+ public void run() {
+ final ClipboardManager clipboardManager =
+ (ClipboardManager) getActivity().getSystemService(
+ Context.CLIPBOARD_SERVICE);
+ clipboardManager.setPrimaryClip(ClipData.newPlainText(null, text));
+ }
+ });
+ }
+
+ void paste() {
+ final WebContents webContents = getWebContents();
+ ThreadUtils.runOnUiThreadBlocking(new Runnable() {
+ @Override
+ public void run() {
+ webContents.paste();
+ }
+ });
+ }
+
+ void selectAll() {
+ final WebContents webContents = getWebContents();
+ ThreadUtils.runOnUiThreadBlocking(new Runnable() {
+ @Override
+ public void run() {
+ webContents.selectAll();
+ }
+ });
+ }
+
+ void collapseSelection() {
+ final WebContents webContents = getWebContents();
+ ThreadUtils.runOnUiThreadBlocking(new Runnable() {
+ @Override
+ public void run() {
+ webContents.collapseSelection();
+ }
+ });
+ }
+
+ /**
+ * Run the {@Callable} on IME thread (or UI thread if not applicable).
+ * @param c The callable
+ * @return The result from running the callable.
+ */
+ <T> T runBlockingOnImeThread(Callable<T> c) throws Exception {
+ return ImeTestUtils.runBlockingOnHandler(mConnectionFactory.getHandler(), c);
+ }
+
+ boolean beginBatchEdit() throws Exception {
+ final ChromiumBaseInputConnection connection = mConnection;
+ return runBlockingOnImeThread(new Callable<Boolean>() {
+ @Override
+ public Boolean call() {
+ return connection.beginBatchEdit();
+ }
+ });
+ }
+
+ boolean endBatchEdit() throws Exception {
+ final ChromiumBaseInputConnection connection = mConnection;
+ return runBlockingOnImeThread(new Callable<Boolean>() {
+ @Override
+ public Boolean call() {
+ return connection.endBatchEdit();
+ }
+ });
+ }
+
+ boolean commitText(final CharSequence text, final int newCursorPosition) throws Exception {
+ final ChromiumBaseInputConnection connection = mConnection;
+ return runBlockingOnImeThread(new Callable<Boolean>() {
+ @Override
+ public Boolean call() {
+ return connection.commitText(text, newCursorPosition);
+ }
+ });
+ }
+
+ boolean setSelection(final int start, final int end) throws Exception {
+ final ChromiumBaseInputConnection connection = mConnection;
+ return runBlockingOnImeThread(new Callable<Boolean>() {
+ @Override
+ public Boolean call() {
+ return connection.setSelection(start, end);
+ }
+ });
+ }
+
+ boolean setComposingRegion(final int start, final int end) throws Exception {
+ final ChromiumBaseInputConnection connection = mConnection;
+ return runBlockingOnImeThread(new Callable<Boolean>() {
+ @Override
+ public Boolean call() {
+ return connection.setComposingRegion(start, end);
+ }
+ });
+ }
+
+ protected boolean setComposingText(final CharSequence text, final int newCursorPosition)
+ throws Exception {
+ final ChromiumBaseInputConnection connection = mConnection;
+ return runBlockingOnImeThread(new Callable<Boolean>() {
+ @Override
+ public Boolean call() {
+ return connection.setComposingText(text, newCursorPosition);
+ }
+ });
+ }
+
+ boolean finishComposingText() throws Exception {
+ final ChromiumBaseInputConnection connection = mConnection;
+ return runBlockingOnImeThread(new Callable<Boolean>() {
+ @Override
+ public Boolean call() {
+ return connection.finishComposingText();
+ }
+ });
+ }
+
+ boolean deleteSurroundingText(final int before, final int after) throws Exception {
+ final ChromiumBaseInputConnection connection = mConnection;
+ return runBlockingOnImeThread(new Callable<Boolean>() {
+ @Override
+ public Boolean call() {
+ return connection.deleteSurroundingText(before, after);
+ }
+ });
+ }
+
+ // Note that deleteSurroundingTextInCodePoints() was introduced in Android N (Api level 24), but
+ // the Android repository used in Chrome is behind that (level 23). So this function can't be
+ // called by keyboard apps currently.
+ @TargetApi(24)
+ boolean deleteSurroundingTextInCodePoints(final int before, final int after) throws Exception {
+ final ThreadedInputConnection connection = (ThreadedInputConnection) mConnection;
+ return runBlockingOnImeThread(new Callable<Boolean>() {
+ @Override
+ public Boolean call() {
+ return connection.deleteSurroundingTextInCodePoints(before, after);
+ }
+ });
+ }
+
+ CharSequence getTextBeforeCursor(final int length, final int flags) throws Exception {
+ final ChromiumBaseInputConnection connection = mConnection;
+ return runBlockingOnImeThread(new Callable<CharSequence>() {
+ @Override
+ public CharSequence call() {
+ return connection.getTextBeforeCursor(length, flags);
+ }
+ });
+ }
+
+ CharSequence getSelectedText(final int flags) throws Exception {
+ final ChromiumBaseInputConnection connection = mConnection;
+ return runBlockingOnImeThread(new Callable<CharSequence>() {
+ @Override
+ public CharSequence call() {
+ return connection.getSelectedText(flags);
+ }
+ });
+ }
+
+ CharSequence getTextAfterCursor(final int length, final int flags) throws Exception {
+ final ChromiumBaseInputConnection connection = mConnection;
+ return runBlockingOnImeThread(new Callable<CharSequence>() {
+ @Override
+ public CharSequence call() {
+ return connection.getTextAfterCursor(length, flags);
+ }
+ });
+ }
+
+ int getCursorCapsMode(final int reqModes) throws Throwable {
+ final ChromiumBaseInputConnection connection = mConnection;
+ return runBlockingOnImeThread(new Callable<Integer>() {
+ @Override
+ public Integer call() {
+ return connection.getCursorCapsMode(reqModes);
+ }
+ });
+ }
+
+ void dispatchKeyEvent(final KeyEvent event) {
+ ThreadUtils.runOnUiThreadBlocking(new Runnable() {
+ @Override
+ public void run() {
+ mImeAdapter.dispatchKeyEvent(event);
+ }
+ });
+ }
+
+ /**
+ * Focus element, wait for a single state update, reset state update list.
+ * @param id ID of the element to focus.
+ */
+ void focusElementAndWaitForStateUpdate(String id)
+ throws InterruptedException, TimeoutException {
+ resetUpdateSelectionList();
+ focusElement(id);
+ waitAndVerifyUpdateSelection(0, 0, 0, -1, -1);
+ resetUpdateSelectionList();
+ }
+
+ void focusElement(final String id) throws InterruptedException, TimeoutException {
+ focusElement(id, true);
+ }
+
+ void focusElement(final String id, boolean shouldShowKeyboard)
+ throws InterruptedException, TimeoutException {
+ DOMUtils.focusNode(getWebContents(), id);
+ assertWaitForKeyboardStatus(shouldShowKeyboard);
+ CriteriaHelper.pollInstrumentationThread(Criteria.equals(id, new Callable<String>() {
+ @Override
+ public String call() throws Exception {
+ return DOMUtils.getFocusedNode(getWebContents());
+ }
+ }));
+ // When we focus another element, the connection may be recreated.
+ mConnection = getInputConnection();
+ }
+
+ static class TestInputConnectionFactory implements ChromiumBaseInputConnection.Factory {
+ private final ChromiumBaseInputConnection.Factory mFactory;
+
+ private final List<Integer> mTextInputTypeList = new ArrayList<>();
+ private EditorInfo mOutAttrs;
+
+ public TestInputConnectionFactory(ChromiumBaseInputConnection.Factory factory) {
+ mFactory = factory;
+ }
+
+ @Override
+ public ChromiumBaseInputConnection initializeAndGet(View view, ImeAdapter imeAdapter,
+ int inputType, int inputFlags, int inputMode, int selectionStart, int selectionEnd,
+ EditorInfo outAttrs) {
+ mTextInputTypeList.add(inputType);
+ mOutAttrs = outAttrs;
+ return mFactory.initializeAndGet(view, imeAdapter, inputType, inputMode, inputFlags,
+ selectionStart, selectionEnd, outAttrs);
+ }
+
+ @Override
+ public Handler getHandler() {
+ return mFactory.getHandler();
+ }
+
+ public Integer[] getTextInputTypeHistory() {
+ Integer[] result = new Integer[mTextInputTypeList.size()];
+ mTextInputTypeList.toArray(result);
+ return result;
+ }
+
+ public void clearTextInputTypeHistory() {
+ mTextInputTypeList.clear();
+ }
+
+ public EditorInfo getOutAttrs() {
+ return mOutAttrs;
+ }
+
+ @Override
+ public void onWindowFocusChanged(boolean gainFocus) {
+ mFactory.onWindowFocusChanged(gainFocus);
+ }
+
+ @Override
+ public void onViewFocusChanged(boolean gainFocus) {
+ mFactory.onViewFocusChanged(gainFocus);
+ }
+
+ @Override
+ public void onViewAttachedToWindow() {
+ mFactory.onViewAttachedToWindow();
+ }
+
+ @Override
+ public void onViewDetachedFromWindow() {
+ mFactory.onViewDetachedFromWindow();
+ }
+ }
+}

Powered by Google App Engine
This is Rietveld 408576698