blob: e4f4dc5e55a2832b91915a4793504fe72ba3b885 [file] [log] [blame]
// 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.android_webview.test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.os.Build;
import android.os.Bundle;
import android.os.LocaleList;
import android.os.Parcel;
import android.support.test.filters.SmallTest;
import android.util.Pair;
import android.util.SparseArray;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewStructure;
import android.view.ViewStructure.HtmlInfo.Builder;
import android.view.WindowManager;
import android.view.autofill.AutofillId;
import android.view.autofill.AutofillValue;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.chromium.android_webview.AwAutofillManager;
import org.chromium.android_webview.AwAutofillProvider;
import org.chromium.android_webview.AwAutofillUMA;
import org.chromium.android_webview.AwContents;
import org.chromium.android_webview.AwContentsClient.AwWebResourceRequest;
import org.chromium.android_webview.AwWebResourceResponse;
import org.chromium.android_webview.test.AwActivityTestRule.TestDependencyFactory;
import org.chromium.autofill.mojom.SubmissionSource;
import org.chromium.base.Log;
import org.chromium.base.metrics.RecordHistogram;
import org.chromium.base.task.PostTask;
import org.chromium.base.test.util.CallbackHelper;
import org.chromium.base.test.util.Feature;
import org.chromium.base.test.util.MetricsUtils;
import org.chromium.base.test.util.MinAndroidSdkLevel;
import org.chromium.components.autofill.AutofillProvider;
import org.chromium.content_public.browser.UiThreadTaskTraits;
import org.chromium.content_public.browser.test.util.DOMUtils;
import org.chromium.content_public.browser.test.util.TestThreadUtils;
import org.chromium.net.test.util.TestWebServer;
import java.io.ByteArrayInputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.TimeoutException;
/**
* Tests for WebView Autofill.
*/
@RunWith(AwJUnit4ClassRunner.class)
@MinAndroidSdkLevel(Build.VERSION_CODES.O)
@SuppressLint("NewApi")
public class AwAutofillTest {
public static final boolean DEBUG = false;
public static final String TAG = "AutofillTest";
public static final String FILE = "/login.html";
public static final String FILE_URL = "file:///android_asset/autofill.html";
public final static int AUTOFILL_VIEW_ENTERED = 1;
public final static int AUTOFILL_VIEW_EXITED = 2;
public final static int AUTOFILL_VALUE_CHANGED = 3;
public final static int AUTOFILL_COMMIT = 4;
public final static int AUTOFILL_CANCEL = 5;
/**
* This class only implements the necessary methods of ViewStructure for testing.
*/
private static class TestViewStructure extends ViewStructure {
/**
* Implementation of HtmlInfo.
*/
public static class AwHtmlInfo extends HtmlInfo {
private String mTag;
private List<Pair<String, String>> mAttributes;
public AwHtmlInfo(String tag, List<Pair<String, String>> attributes) {
mTag = tag;
mAttributes = attributes;
}
@Override
public List<Pair<String, String>> getAttributes() {
return mAttributes;
}
public String getAttribute(String attribute) {
for (Pair<String, String> pair : mAttributes) {
if (attribute.equals(pair.first)) {
return pair.second;
}
}
return null;
}
@Override
public String getTag() {
return mTag;
}
}
/**
* Implementation of Builder
*/
public static class AwBuilder extends Builder {
private String mTag;
private ArrayList<Pair<String, String>> mAttributes;
public AwBuilder(String tag) {
mTag = tag;
mAttributes = new ArrayList<Pair<String, String>>();
}
@Override
public Builder addAttribute(String name, String value) {
mAttributes.add(new Pair<String, String>(name, value));
return this;
}
@Override
public HtmlInfo build() {
return new AwHtmlInfo(mTag, mAttributes);
}
}
public TestViewStructure() {
mChildren = new ArrayList<TestViewStructure>();
}
@Override
public void setAlpha(float alpha) {}
@Override
public void setAccessibilityFocused(boolean state) {}
@Override
public void setCheckable(boolean state) {}
@Override
public void setChecked(boolean state) {
mChecked = state;
}
public boolean getChecked() {
return mChecked;
}
@Override
public void setActivated(boolean state) {}
@Override
public CharSequence getText() {
return null;
}
@Override
public int getTextSelectionStart() {
return 0;
}
@Override
public int getTextSelectionEnd() {
return 0;
}
@Override
public CharSequence getHint() {
return mHint;
}
@Override
public Bundle getExtras() {
return null;
}
@Override
public boolean hasExtras() {
return false;
}
@Override
public void setChildCount(int num) {}
@Override
public int addChildCount(int num) {
int index = mChildCount;
mChildCount += num;
mChildren.ensureCapacity(mChildCount);
return index;
}
@Override
public int getChildCount() {
return mChildren.size();
}
@Override
public ViewStructure newChild(int index) {
TestViewStructure viewStructure = new TestViewStructure();
if (index < mChildren.size()) {
mChildren.set(index, viewStructure);
} else if (index == mChildren.size()) {
mChildren.add(viewStructure);
} else {
// Failed intentionally, we shouldn't run into this case.
mChildren.add(index, viewStructure);
}
return viewStructure;
}
public TestViewStructure getChild(int index) {
return mChildren.get(index);
}
@Override
public ViewStructure asyncNewChild(int index) {
return null;
}
@Override
public void asyncCommit() {}
@Override
public AutofillId getAutofillId() {
Parcel parcel = Parcel.obtain();
// Check AutofillId(Parcel) in AutofillId.java, always set parent id as 0.
parcel.writeInt(0);
parcel.writeInt(1);
parcel.writeInt(mId);
return AutofillId.CREATOR.createFromParcel(parcel);
}
@Override
public Builder newHtmlInfoBuilder(String tag) {
return new AwBuilder(tag);
}
@Override
public void setAutofillHints(String[] arg0) {
mAutofillHints = arg0.clone();
}
public String[] getAutofillHints() {
if (mAutofillHints == null) return null;
return mAutofillHints.clone();
}
@Override
public void setAutofillId(AutofillId arg0) {}
@Override
public void setAutofillId(AutofillId arg0, int arg1) {
mId = arg1;
}
public int getId() {
return mId;
}
@Override
public void setAutofillOptions(CharSequence[] arg0) {
mAutofillOptions = arg0.clone();
}
public CharSequence[] getAutofillOptions() {
if (mAutofillOptions == null) return null;
return mAutofillOptions.clone();
}
@Override
public void setAutofillType(int arg0) {
mAutofillType = arg0;
}
public int getAutofillType() {
return mAutofillType;
}
@Override
public void setAutofillValue(AutofillValue arg0) {
mAutofillValue = arg0;
}
public AutofillValue getAutofillValue() {
return mAutofillValue;
}
@Override
public void setId(int id, String packageName, String typeName, String entryName) {}
@Override
public void setDimens(int left, int top, int scrollX, int scrollY, int width, int height) {}
@Override
public void setElevation(float elevation) {}
@Override
public void setEnabled(boolean state) {}
@Override
public void setClickable(boolean state) {}
@Override
public void setLongClickable(boolean state) {}
@Override
public void setContextClickable(boolean state) {}
@Override
public void setFocusable(boolean state) {}
@Override
public void setFocused(boolean state) {}
@Override
public void setClassName(String className) {
mClassName = className;
}
public String getClassName() {
return mClassName;
}
@Override
public void setContentDescription(CharSequence contentDescription) {}
@Override
public void setDataIsSensitive(boolean arg0) {
mDataIsSensitive = arg0;
}
public boolean getDataIsSensitive() {
return mDataIsSensitive;
}
@Override
public void setHint(CharSequence hint) {
mHint = hint;
}
@Override
public void setHtmlInfo(HtmlInfo arg0) {
mAwHtmlInfo = (AwHtmlInfo) arg0;
}
public AwHtmlInfo getHtmlInfo() {
return mAwHtmlInfo;
}
@Override
public void setInputType(int arg0) {}
@Override
public void setLocaleList(LocaleList arg0) {}
@Override
public void setOpaque(boolean arg0) {}
@Override
public void setTransformation(Matrix matrix) {}
@Override
public void setVisibility(int visibility) {}
@Override
public void setSelected(boolean state) {}
@Override
public void setText(CharSequence text) {}
@Override
public void setText(CharSequence text, int selectionStart, int selectionEnd) {}
@Override
public void setTextStyle(float size, int fgColor, int bgColor, int style) {}
@Override
public void setTextLines(int[] charOffsets, int[] baselines) {}
@Override
public void setWebDomain(String webDomain) {
mWebDomain = webDomain;
}
public String getWebDomain() {
return mWebDomain;
}
private int mAutofillType;
private CharSequence mHint;
private String[] mAutofillHints;
private int mId;
private String mClassName;
private String mWebDomain;
private int mChildCount;
private ArrayList<TestViewStructure> mChildren;
private CharSequence[] mAutofillOptions;
private AutofillValue mAutofillValue;
private boolean mDataIsSensitive;
private AwHtmlInfo mAwHtmlInfo;
private boolean mChecked;
}
// crbug.com/776230: On Android L, declaring variables of unsupported classes causes an error.
// Wrapped them in a class to avoid it.
private static class TestValues {
public TestViewStructure testViewStructure;
public ArrayList<Pair<Integer, AutofillValue>> changedValues;
}
private class TestAwAutofillManager extends AwAutofillManager {
private boolean mDisabled;
public TestAwAutofillManager(Context context) {
super(context);
}
public void setDisabled() {
mDisabled = true;
}
@Override
public boolean isDisabled() {
return mDisabled;
}
@Override
public void notifyVirtualViewEntered(View parent, int childId, Rect absBounds) {
if (DEBUG) Log.i(TAG, "notifyVirtualViewEntered");
mEventQueue.add(AUTOFILL_VIEW_ENTERED);
mCallbackHelper.notifyCalled();
}
@Override
public void notifyVirtualViewExited(View parent, int childId) {
if (DEBUG) Log.i(TAG, "notifyVirtualViewExited");
mEventQueue.add(AUTOFILL_VIEW_EXITED);
mCallbackHelper.notifyCalled();
}
@Override
public void notifyVirtualValueChanged(View parent, int childId, AutofillValue value) {
if (DEBUG) Log.i(TAG, "notifyVirtualValueChanged");
if (mTestValues.changedValues == null) {
mTestValues.changedValues = new ArrayList<Pair<Integer, AutofillValue>>();
}
mTestValues.changedValues.add(new Pair<Integer, AutofillValue>(childId, value));
mEventQueue.add(AUTOFILL_VALUE_CHANGED);
mCallbackHelper.notifyCalled();
}
@Override
public void commit(int submissionSource) {
if (DEBUG) Log.i(TAG, "commit");
mEventQueue.add(AUTOFILL_COMMIT);
mSubmissionSource = submissionSource;
mCallbackHelper.notifyCalled();
}
@Override
public void cancel() {
if (DEBUG) Log.i(TAG, "cancel");
mEventQueue.add(AUTOFILL_CANCEL);
mCallbackHelper.notifyCalled();
}
}
private static class AwAutofillTestClient extends TestAwContentsClient {
public interface ShouldInterceptRequestImpl {
AwWebResourceResponse shouldInterceptRequest(AwWebResourceRequest request);
}
private ShouldInterceptRequestImpl mShouldInterceptRequestImpl;
public void setShouldInterceptRequestImpl(ShouldInterceptRequestImpl impl) {
mShouldInterceptRequestImpl = impl;
}
@Override
public AwWebResourceResponse shouldInterceptRequest(AwWebResourceRequest request) {
AwWebResourceResponse response = null;
if (mShouldInterceptRequestImpl != null) {
response = mShouldInterceptRequestImpl.shouldInterceptRequest(request);
}
if (response != null) return response;
return super.shouldInterceptRequest(request);
}
}
private static class AwAutofillSessionUMATestHelper {
private final static String DATA =
"<html><head></head><body><form action='a.html' name='formname' id='formid'>"
+ "<label>User Name:</label>"
+ "<input type='text' id='text1' name='username'"
+ " placeholder='placeholder@placeholder.com' autocomplete='username name'>"
+ "<input type='submit'>"
+ "</form>"
+ "<form><input type='text' id='text2'/></form></body></html>";
private final static int TOTAL_CONTROLS = 1; // text1
public final static int NO_FORM_SUBMISSION = -1;
public AwAutofillSessionUMATestHelper(AwAutofillTest test) {
mTest = test;
initDeltaSamples();
}
public int getSessionValue() {
TestThreadUtils.runOnUiThreadBlocking(
() -> { mSessionValue = getUMAEnumerateValue(mSessionDelta); });
return mSessionValue;
}
public int getSubmissionSourceValue() {
TestThreadUtils.runOnUiThreadBlocking(
() -> { mSourceValue = getUMAEnumerateValue(mSubmissionSourceDelta); });
return mSourceValue;
}
private int getUMAEnumerateValue(HashMap<MetricsUtils.HistogramDelta, Integer> deltas) {
int value = NO_FORM_SUBMISSION;
for (MetricsUtils.HistogramDelta delta : deltas.keySet()) {
if (delta.getDelta() != 0) {
assertEquals(NO_FORM_SUBMISSION, value);
value = deltas.get(delta);
}
}
return value;
}
public void triggerAutofill(TestWebServer webServer) throws Throwable {
final String url = webServer.setResponse(FILE, DATA, null);
mTest.loadUrlSync(url);
mTest.executeJavaScriptAndWaitForResult("document.getElementById('text1').select();");
mTest.dispatchDownAndUpKeyEvents(KeyEvent.KEYCODE_A);
// Note that we currently call ENTER/EXIT one more time.
mCnt += mTest.waitForCallbackAndVerifyTypes(mCnt,
new Integer[] {AUTOFILL_CANCEL, AUTOFILL_VIEW_ENTERED, AUTOFILL_VIEW_EXITED,
AUTOFILL_VIEW_ENTERED, AUTOFILL_VALUE_CHANGED});
}
public void simulateUserSelectSuggestion() throws Throwable {
// Simulate user select suggestion
TestViewStructure viewStructure = mTest.mTestValues.testViewStructure;
assertNotNull(viewStructure);
assertEquals(TOTAL_CONTROLS, viewStructure.getChildCount());
TestViewStructure child0 = viewStructure.getChild(0);
// Autofill form and verify filled values.
SparseArray<AutofillValue> values = new SparseArray<AutofillValue>();
values.append(child0.getId(), AutofillValue.forText("example@example.com"));
mCnt = mTest.getCallbackCount();
mTest.clearChangedValues();
mTest.invokeAutofill(values);
mCnt += mTest.waitForCallbackAndVerifyTypes(
mCnt, new Integer[] {AUTOFILL_VALUE_CHANGED});
}
public void simulateUserChangeAutofilledField() throws Throwable {
mTest.executeJavaScriptAndWaitForResult("document.getElementById('text1').select();");
mTest.dispatchDownAndUpKeyEvents(KeyEvent.KEYCODE_B);
mCnt += mTest.waitForCallbackAndVerifyTypes(mCnt,
new Integer[] {
AUTOFILL_VIEW_EXITED, AUTOFILL_VIEW_ENTERED, AUTOFILL_VALUE_CHANGED});
}
public void submitForm() throws Throwable {
mTest.executeJavaScriptAndWaitForResult("document.getElementById('formid').submit();");
mCnt += mTest.waitForCallbackAndVerifyTypes(
mCnt, new Integer[] {AUTOFILL_VALUE_CHANGED, AUTOFILL_COMMIT});
}
public void startNewSession() throws Throwable {
// Start a new session by moving focus to another form.
mTest.executeJavaScriptAndWaitForResult("document.getElementById('text2').select();");
mTest.dispatchDownAndUpKeyEvents(KeyEvent.KEYCODE_A);
// Note that we currently call ENTER/EXIT one more time.
mCnt += mTest.waitForCallbackAndVerifyTypes(mCnt,
new Integer[] {AUTOFILL_VIEW_EXITED, AUTOFILL_CANCEL, AUTOFILL_VIEW_ENTERED,
AUTOFILL_VIEW_EXITED, AUTOFILL_VIEW_ENTERED, AUTOFILL_VALUE_CHANGED});
}
public void simulateUserChangeField() throws Throwable {
mTest.executeJavaScriptAndWaitForResult("document.getElementById('text1').select();");
mTest.dispatchDownAndUpKeyEvents(KeyEvent.KEYCODE_B);
mCnt += mTest.waitForCallbackAndVerifyTypes(mCnt,
new Integer[] {
AUTOFILL_VIEW_EXITED, AUTOFILL_VIEW_ENTERED, AUTOFILL_VALUE_CHANGED});
}
private void initDeltaSamples() {
TestThreadUtils.runOnUiThreadBlocking(() -> {
mSessionDelta = new HashMap<MetricsUtils.HistogramDelta, Integer>();
for (int i = 0; i < AwAutofillUMA.AUTOFILL_SESSION_HISTOGRAM_COUNT; i++) {
mSessionDelta.put(
new MetricsUtils.HistogramDelta(
AwAutofillUMA.UMA_AUTOFILL_WEBVIEW_AUTOFILL_SESSION, i),
i);
}
mSubmissionSourceDelta = new HashMap<MetricsUtils.HistogramDelta, Integer>();
for (int i = 0; i < AwAutofillUMA.SUBMISSION_SOURCE_HISTOGRAM_COUNT; i++) {
mSubmissionSourceDelta.put(
new MetricsUtils.HistogramDelta(
AwAutofillUMA.UMA_AUTOFILL_WEBVIEW_SUBMISSION_SOURCE, i),
i);
}
mAutofillWebViewViewEnabled = new MetricsUtils.HistogramDelta(
AwAutofillUMA.UMA_AUTOFILL_WEBVIEW_ENABLED, 1 /*true*/);
mAutofillWebViewViewDisabled = new MetricsUtils.HistogramDelta(
AwAutofillUMA.UMA_AUTOFILL_WEBVIEW_ENABLED, 0 /*false*/);
mAutofillWebViewCreatedByActivityContext = new MetricsUtils.HistogramDelta(
AwAutofillUMA.UMA_AUTOFILL_WEBVIEW_CREATED_BY_ACTIVITY_CONTEXT, 1);
mAutofillWebViewCreatedByAppContext = new MetricsUtils.HistogramDelta(
AwAutofillUMA.UMA_AUTOFILL_WEBVIEW_CREATED_BY_ACTIVITY_CONTEXT, 0);
mUserChangedAutofilledField = new MetricsUtils.HistogramDelta(
AwAutofillUMA.UMA_AUTOFILL_USER_CHANGED_AUTOFILLED_FIELD, 1 /*true*/);
mUserChangedNonAutofilledField = new MetricsUtils.HistogramDelta(
AwAutofillUMA.UMA_AUTOFILL_USER_CHANGED_AUTOFILLED_FIELD, 0 /*falsTe*/);
});
}
public int getHistogramSampleCount(String name) throws Throwable {
TestThreadUtils.runOnUiThreadBlocking(() -> {
mHistogramSimpleCount =
Integer.valueOf(RecordHistogram.getHistogramTotalCountForTesting(name));
});
return mHistogramSimpleCount;
}
public void verifyAutofillEnabled() throws Throwable {
TestThreadUtils.runOnUiThreadBlocking(() -> {
assertEquals(1, mAutofillWebViewViewEnabled.getDelta());
assertEquals(0, mAutofillWebViewViewDisabled.getDelta());
});
}
public void verifyAutofillDisabled() throws Throwable {
TestThreadUtils.runOnUiThreadBlocking(() -> {
assertEquals(0, mAutofillWebViewViewEnabled.getDelta());
assertEquals(1, mAutofillWebViewViewDisabled.getDelta());
});
}
public void verifyUserChangedAutofilledField() throws Throwable {
TestThreadUtils.runOnUiThreadBlocking(() -> {
assertEquals(0, mUserChangedNonAutofilledField.getDelta());
assertEquals(1, mUserChangedAutofilledField.getDelta());
});
}
public void verifyUserChangedNonAutofilledField() throws Throwable {
// User changed the form, but not the autofilled field.
TestThreadUtils.runOnUiThreadBlocking(() -> {
assertEquals(1, mUserChangedNonAutofilledField.getDelta());
assertEquals(0, mUserChangedAutofilledField.getDelta());
});
}
public void verifyUserDidntChangeForm() throws Throwable {
// User didn't change the form at all.
TestThreadUtils.runOnUiThreadBlocking(() -> {
assertEquals(0, mUserChangedNonAutofilledField.getDelta());
assertEquals(0, mUserChangedAutofilledField.getDelta());
});
}
public void verifyWebViewCreatedByActivityContext() throws Throwable {
TestThreadUtils.runOnUiThreadBlocking(() -> {
assertEquals(1, mAutofillWebViewCreatedByActivityContext.getDelta());
assertEquals(0, mAutofillWebViewCreatedByAppContext.getDelta());
});
}
private int mCnt;
private AwAutofillTest mTest;
private volatile Integer mSessionValue;
private HashMap<MetricsUtils.HistogramDelta, Integer> mSessionDelta;
private MetricsUtils.HistogramDelta mAutofillWebViewViewEnabled;
private MetricsUtils.HistogramDelta mAutofillWebViewViewDisabled;
private MetricsUtils.HistogramDelta mUserChangedAutofilledField;
private MetricsUtils.HistogramDelta mUserChangedNonAutofilledField;
private MetricsUtils.HistogramDelta mAutofillWebViewCreatedByActivityContext;
private MetricsUtils.HistogramDelta mAutofillWebViewCreatedByAppContext;
private volatile Integer mSourceValue;
private HashMap<MetricsUtils.HistogramDelta, Integer> mSubmissionSourceDelta;
private volatile Integer mHistogramSimpleCount;
}
@Rule
public AwActivityTestRule mRule = new AwActivityTestRule();
private AwTestContainerView mTestContainerView;
private AwAutofillTestClient mContentsClient;
private CallbackHelper mCallbackHelper = new CallbackHelper();
private AwContents mAwContents;
private ConcurrentLinkedQueue<Integer> mEventQueue = new ConcurrentLinkedQueue<>();
private TestValues mTestValues = new TestValues();
private int mSubmissionSource;
private TestAwAutofillManager mTestAwAutofillManager;
private AwAutofillSessionUMATestHelper mUMATestHelper;
@Before
public void setUp() throws Exception {
mUMATestHelper = new AwAutofillSessionUMATestHelper(this);
mContentsClient = new AwAutofillTestClient();
mTestContainerView = mRule.createAwTestContainerViewOnMainSync(
mContentsClient, false, new TestDependencyFactory() {
@Override
public AutofillProvider createAutofillProvider(
Context context, ViewGroup containerView) {
mTestAwAutofillManager = new TestAwAutofillManager(context);
return new AwAutofillProvider(
containerView, mTestAwAutofillManager, context);
}
});
mAwContents = mTestContainerView.getAwContents();
AwActivityTestRule.enableJavaScriptOnUiThread(mAwContents);
}
@Test
@SmallTest
@Feature({"AndroidWebView"})
public void testTouchingFormWithAdjustResize() throws Throwable {
PostTask.runOrPostTask(UiThreadTaskTraits.DEFAULT, () -> {
mRule.getActivity().getWindow().setSoftInputMode(
WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
});
internalTestTriggerTest();
}
@Test
@SmallTest
@Feature({"AndroidWebView"})
public void testTouchingFormWithAdjustPan() throws Throwable {
PostTask.runOrPostTask(UiThreadTaskTraits.DEFAULT, () -> {
mRule.getActivity().getWindow().setSoftInputMode(
WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN);
});
internalTestTriggerTest();
}
private void internalTestTriggerTest() throws Throwable {
TestWebServer webServer = TestWebServer.start();
final String data = "<html><head></head><body><form action='a.html' name='formname'>"
+ "<input type='text' id='text1' name='username'"
+ " placeholder='placeholder@placeholder.com' autocomplete='username name'>"
+ "<input type='submit'>"
+ "</form></body></html>";
try {
int cnt = 0;
final String url = webServer.setResponse(FILE, data, null);
loadUrlSync(url);
DOMUtils.waitForNonZeroNodeBounds(mAwContents.getWebContents(), "text1");
// Note that we currently depend on keyboard app's behavior.
// TODO(changwan): mock out IME interaction.
Assert.assertTrue(DOMUtils.clickNode(mTestContainerView.getWebContents(), "text1"));
cnt += waitForCallbackAndVerifyTypes(
cnt, new Integer[] {AUTOFILL_CANCEL, AUTOFILL_VIEW_ENTERED});
dispatchDownAndUpKeyEvents(KeyEvent.KEYCODE_A);
// Note that we currently call ENTER/EXIT one more time.
waitForCallbackAndVerifyTypes(cnt,
new Integer[] {
AUTOFILL_VIEW_EXITED, AUTOFILL_VIEW_ENTERED, AUTOFILL_VALUE_CHANGED});
executeJavaScriptAndWaitForResult("document.getElementById('text1').blur();");
waitForCallbackAndVerifyTypes(cnt, new Integer[] {AUTOFILL_VIEW_EXITED});
} finally {
webServer.shutdown();
}
}
@Test
@SmallTest
@Feature({"AndroidWebView"})
public void testBasicAutofill() throws Throwable {
TestWebServer webServer = TestWebServer.start();
final String data = "<html><head></head><body><form action='a.html' name='formname'>"
+ "<label>User Name:</label>"
+ "<input type='text' id='text1' name='name' maxlength='30'"
+ " placeholder='placeholder@placeholder.com' autocomplete='name given-name'>"
+ "<input type='checkbox' id='checkbox1' name='showpassword'>"
+ "<select id='select1' name='month'>"
+ "<option value='1'>Jan</option>"
+ "<option value='2'>Feb</option>"
+ "</select><textarea id='textarea1'></textarea>"
+ "<div contenteditable id='div1'>hello</div>"
+ "<input type='submit'>"
+ "<input type='reset' id='reset1'>"
+ "<input type='color' id='color1'><input type='file' id='file1'>"
+ "<input type='image' id='image1'>"
+ "</form></body></html>";
final int totalControls = 4; // text1, checkbox1, select1, textarea1
try {
int cnt = 0;
final String url = webServer.setResponse(FILE, data, null);
loadUrlSync(url);
executeJavaScriptAndWaitForResult("document.getElementById('text1').select();");
dispatchDownAndUpKeyEvents(KeyEvent.KEYCODE_A);
// Note that we currently call ENTER/EXIT one more time.
cnt += waitForCallbackAndVerifyTypes(cnt,
new Integer[] {AUTOFILL_CANCEL, AUTOFILL_VIEW_ENTERED, AUTOFILL_VIEW_EXITED,
AUTOFILL_VIEW_ENTERED, AUTOFILL_VALUE_CHANGED});
invokeOnProvideAutoFillVirtualStructure();
TestViewStructure viewStructure = mTestValues.testViewStructure;
assertNotNull(viewStructure);
assertEquals(totalControls, viewStructure.getChildCount());
// Verify form filled correctly in ViewStructure.
URL pageURL = new URL(url);
String webDomain =
new URL(pageURL.getProtocol(), pageURL.getHost(), pageURL.getPort(), "/")
.toString();
assertEquals(webDomain, viewStructure.getWebDomain());
// WebView shouldn't set class name.
assertNull(viewStructure.getClassName());
TestViewStructure.AwHtmlInfo htmlInfoForm = viewStructure.getHtmlInfo();
assertEquals("form", htmlInfoForm.getTag());
assertEquals("formname", htmlInfoForm.getAttribute("name"));
// Verify input text control filled correctly in ViewStructure.
TestViewStructure child0 = viewStructure.getChild(0);
assertEquals(View.AUTOFILL_TYPE_TEXT, child0.getAutofillType());
assertEquals("placeholder@placeholder.com", child0.getHint());
assertEquals("name", child0.getAutofillHints()[0]);
assertEquals("given-name", child0.getAutofillHints()[1]);
TestViewStructure.AwHtmlInfo htmlInfo0 = child0.getHtmlInfo();
assertEquals("text", htmlInfo0.getAttribute("type"));
assertEquals("text1", htmlInfo0.getAttribute("id"));
assertEquals("name", htmlInfo0.getAttribute("name"));
assertEquals("User Name:", htmlInfo0.getAttribute("label"));
assertEquals("30", htmlInfo0.getAttribute("maxlength"));
assertEquals("NAME_FIRST", htmlInfo0.getAttribute("ua-autofill-hints"));
// Verify checkbox control filled correctly in ViewStructure.
TestViewStructure child1 = viewStructure.getChild(1);
assertEquals(View.AUTOFILL_TYPE_TOGGLE, child1.getAutofillType());
assertEquals("", child1.getHint());
assertNull(child1.getAutofillHints());
TestViewStructure.AwHtmlInfo htmlInfo1 = child1.getHtmlInfo();
assertEquals("checkbox", htmlInfo1.getAttribute("type"));
assertEquals("checkbox1", htmlInfo1.getAttribute("id"));
assertEquals("showpassword", htmlInfo1.getAttribute("name"));
assertEquals("", htmlInfo1.getAttribute("label"));
assertNull(htmlInfo1.getAttribute("maxlength"));
assertNull(htmlInfo1.getAttribute("ua-autofill-hints"));
// Verify select control filled correctly in ViewStructure.
TestViewStructure child2 = viewStructure.getChild(2);
assertEquals(View.AUTOFILL_TYPE_LIST, child2.getAutofillType());
assertEquals("", child2.getHint());
assertNull(child2.getAutofillHints());
TestViewStructure.AwHtmlInfo htmlInfo2 = child2.getHtmlInfo();
assertEquals("month", htmlInfo2.getAttribute("name"));
assertEquals("select1", htmlInfo2.getAttribute("id"));
CharSequence[] options = child2.getAutofillOptions();
assertEquals("Jan", options[0]);
assertEquals("Feb", options[1]);
// Verify textarea control is filled correctly in ViewStructure.
TestViewStructure child3 = viewStructure.getChild(3);
assertEquals(View.AUTOFILL_TYPE_TEXT, child3.getAutofillType());
assertEquals("", child3.getHint());
assertNull(child3.getAutofillHints());
TestViewStructure.AwHtmlInfo htmlInfo3 = child3.getHtmlInfo();
assertEquals("textarea1", htmlInfo3.getAttribute("name"));
// Autofill form and verify filled values.
SparseArray<AutofillValue> values = new SparseArray<AutofillValue>();
values.append(child0.getId(), AutofillValue.forText("example@example.com"));
values.append(child1.getId(), AutofillValue.forToggle(true));
values.append(child2.getId(), AutofillValue.forList(1));
values.append(child3.getId(), AutofillValue.forText("aaa"));
cnt = getCallbackCount();
clearChangedValues();
invokeAutofill(values);
waitForCallbackAndVerifyTypes(cnt,
new Integer[] {AUTOFILL_VALUE_CHANGED, AUTOFILL_VALUE_CHANGED,
AUTOFILL_VALUE_CHANGED, AUTOFILL_VALUE_CHANGED});
// Verify form filled by Javascript
String value0 =
executeJavaScriptAndWaitForResult("document.getElementById('text1').value;");
assertEquals("\"example@example.com\"", value0);
String value1 = executeJavaScriptAndWaitForResult(
"document.getElementById('checkbox1').value;");
assertEquals("\"on\"", value1);
String value2 =
executeJavaScriptAndWaitForResult("document.getElementById('select1').value;");
assertEquals("\"2\"", value2);
String value3 = executeJavaScriptAndWaitForResult(
"document.getElementById('textarea1').value;");
assertEquals("\"aaa\"", value3);
ArrayList<Pair<Integer, AutofillValue>> changedValues = getChangedValues();
assertEquals("example@example.com", changedValues.get(0).second.getTextValue());
assertTrue(changedValues.get(1).second.getToggleValue());
assertEquals(1, changedValues.get(2).second.getListValue());
} finally {
webServer.shutdown();
}
}
@Test
@SmallTest
@Feature({"AndroidWebView"})
public void testNotifyVirtualValueChanged() throws Throwable {
TestWebServer webServer = TestWebServer.start();
final String data = "<html><head></head><body><form action='a.html' name='formname'>"
+ "<input type='text' id='text1' name='username'"
+ " placeholder='placeholder@placeholder.com' autocomplete='username name'>"
+ "</form></body></html>";
try {
final String url = webServer.setResponse(FILE, data, null);
loadUrlSync(url);
int cnt = 0;
executeJavaScriptAndWaitForResult("document.getElementById('text1').select();");
dispatchDownAndUpKeyEvents(KeyEvent.KEYCODE_A);
// Note that we currently call ENTER/EXIT one more time.
cnt += waitForCallbackAndVerifyTypes(cnt,
new Integer[] {AUTOFILL_CANCEL, AUTOFILL_VIEW_ENTERED, AUTOFILL_VIEW_EXITED,
AUTOFILL_VIEW_ENTERED, AUTOFILL_VALUE_CHANGED});
ArrayList<Pair<Integer, AutofillValue>> values = getChangedValues();
// Check if NotifyVirtualValueChanged() called and value is 'a'.
assertEquals(1, values.size());
assertEquals("a", values.get(0).second.getTextValue());
dispatchDownAndUpKeyEvents(KeyEvent.KEYCODE_B);
// Check if NotifyVirtualValueChanged() called again, first value is 'a',
// second value is 'ab', and both time has the same id.
waitForCallbackAndVerifyTypes(cnt,
new Integer[] {
AUTOFILL_VIEW_EXITED, AUTOFILL_VIEW_ENTERED, AUTOFILL_VALUE_CHANGED});
values = getChangedValues();
assertEquals(2, values.size());
assertEquals("a", values.get(0).second.getTextValue());
assertEquals("ab", values.get(1).second.getTextValue());
assertEquals(values.get(0).first, values.get(1).first);
} finally {
webServer.shutdown();
}
}
@Test
@SmallTest
@Feature({"AndroidWebView"})
public void testJavascriptNotTriggerNotifyVirtualValueChanged() throws Throwable {
TestWebServer webServer = TestWebServer.start();
final String data = "<html><head></head><body><form action='a.html' name='formname'>"
+ "<input type='text' id='text1' name='username'"
+ " placeholder='placeholder@placeholder.com' autocomplete='username name'>"
+ "</form></body></html>";
try {
final String url = webServer.setResponse(FILE, data, null);
loadUrlSync(url);
int cnt = 0;
executeJavaScriptAndWaitForResult("document.getElementById('text1').select();");
dispatchDownAndUpKeyEvents(KeyEvent.KEYCODE_A);
// Note that we currently call ENTER/EXIT one more time.
cnt += waitForCallbackAndVerifyTypes(cnt,
new Integer[] {AUTOFILL_CANCEL, AUTOFILL_VIEW_ENTERED, AUTOFILL_VIEW_EXITED,
AUTOFILL_VIEW_ENTERED, AUTOFILL_VALUE_CHANGED});
ArrayList<Pair<Integer, AutofillValue>> values = getChangedValues();
// Check if NotifyVirtualValueChanged() called and value is 'a'.
assertEquals(1, values.size());
assertEquals("a", values.get(0).second.getTextValue());
executeJavaScriptAndWaitForResult("document.getElementById('text1').value='c';");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
// There is no AUTOFILL_CANCEL from Android P.
assertEquals(4, getCallbackCount());
} else {
assertEquals(5, getCallbackCount());
}
dispatchDownAndUpKeyEvents(KeyEvent.KEYCODE_B);
// Check if NotifyVirtualValueChanged() called one more time and value is 'cb', this
// means javascript change didn't trigger the NotifyVirtualValueChanged().
waitForCallbackAndVerifyTypes(cnt,
new Integer[] {
AUTOFILL_VIEW_EXITED, AUTOFILL_VIEW_ENTERED, AUTOFILL_VALUE_CHANGED});
values = getChangedValues();
assertEquals(2, values.size());
assertEquals("a", values.get(0).second.getTextValue());
assertEquals("cb", values.get(1).second.getTextValue());
assertEquals(values.get(0).first, values.get(1).first);
} finally {
webServer.shutdown();
}
}
@Test
@SmallTest
@Feature({"AndroidWebView"})
public void testCommit() throws Throwable {
TestWebServer webServer = TestWebServer.start();
final String data =
"<html><head></head><body><form action='a.html' name='formname' id='formid'>"
+ "<input type='text' id='text1' name='username'"
+ " placeholder='placeholder@placeholder.com' autocomplete='username name'>"
+ "<input type='password' id='passwordid' name='passwordname'"
+ "<input type='submit'>"
+ "</form></body></html>";
try {
final String url = webServer.setResponse(FILE, data, null);
loadUrlSync(url);
int cnt = 0;
executeJavaScriptAndWaitForResult("document.getElementById('text1').select();");
dispatchDownAndUpKeyEvents(KeyEvent.KEYCODE_A);
// Note that we currently call ENTER/EXIT one more time.
cnt += waitForCallbackAndVerifyTypes(cnt,
new Integer[] {AUTOFILL_CANCEL, AUTOFILL_VIEW_ENTERED, AUTOFILL_VIEW_EXITED,
AUTOFILL_VIEW_ENTERED, AUTOFILL_VALUE_CHANGED});
invokeOnProvideAutoFillVirtualStructure();
// Fill the password.
executeJavaScriptAndWaitForResult("document.getElementById('passwordid').select();");
cnt += waitForCallbackAndVerifyTypes(cnt,
new Integer[] {
AUTOFILL_VIEW_EXITED, AUTOFILL_VIEW_ENTERED, AUTOFILL_VALUE_CHANGED});
dispatchDownAndUpKeyEvents(KeyEvent.KEYCODE_B);
cnt += waitForCallbackAndVerifyTypes(cnt,
new Integer[] {
AUTOFILL_VIEW_EXITED, AUTOFILL_VIEW_ENTERED, AUTOFILL_VALUE_CHANGED});
clearChangedValues();
// Submit form.
executeJavaScriptAndWaitForResult("document.getElementById('formid').submit();");
waitForCallbackAndVerifyTypes(cnt,
new Integer[] {
AUTOFILL_VALUE_CHANGED, AUTOFILL_VALUE_CHANGED, AUTOFILL_COMMIT});
ArrayList<Pair<Integer, AutofillValue>> values = getChangedValues();
assertEquals(2, values.size());
assertEquals("a", values.get(0).second.getTextValue());
assertEquals("b", values.get(1).second.getTextValue());
assertEquals(SubmissionSource.FORM_SUBMISSION, mSubmissionSource);
} finally {
webServer.shutdown();
}
}
@Test
@SmallTest
@Feature({"AndroidWebView"})
public void testLoadFileURL() throws Throwable {
int cnt = 0;
loadUrlSync(FILE_URL);
executeJavaScriptAndWaitForResult("document.getElementById('text1').select();");
dispatchDownAndUpKeyEvents(KeyEvent.KEYCODE_A);
// Cancel called for the first query.
// Note that we currently call ENTER/EXIT one more time.
waitForCallbackAndVerifyTypes(cnt,
new Integer[] {AUTOFILL_CANCEL, AUTOFILL_VIEW_ENTERED, AUTOFILL_VIEW_EXITED,
AUTOFILL_VIEW_ENTERED, AUTOFILL_VALUE_CHANGED});
}
@Test
@SmallTest
@Feature({"AndroidWebView"})
public void testMovingToOtherForm() throws Throwable {
TestWebServer webServer = TestWebServer.start();
final String data =
"<html><head></head><body><form action='a.html' name='formname' id='formid'>"
+ "<input type='text' id='text1' name='username'"
+ " placeholder='placeholder@placeholder.com' autocomplete='username name'>"
+ "<input type='submit'></form>"
+ "<form action='a.html' name='formname' id='formid2'>"
+ "<input type='text' id='text2' name='username'"
+ " placeholder='placeholder@placeholder.com' autocomplete='username name'>"
+ "<input type='submit'>"
+ "</form></body></html>";
try {
int cnt = 0;
final String url = webServer.setResponse(FILE, data, null);
loadUrlSync(url);
executeJavaScriptAndWaitForResult("document.getElementById('text1').select();");
dispatchDownAndUpKeyEvents(KeyEvent.KEYCODE_A);
// Note that we currently call ENTER/EXIT one more time.
cnt += waitForCallbackAndVerifyTypes(cnt,
new Integer[] {AUTOFILL_CANCEL, AUTOFILL_VIEW_ENTERED, AUTOFILL_VIEW_EXITED,
AUTOFILL_VIEW_ENTERED, AUTOFILL_VALUE_CHANGED});
// Move to form2, cancel() should be called again.
executeJavaScriptAndWaitForResult("document.getElementById('text2').select();");
cnt += waitForCallbackAndVerifyTypes(cnt, new Integer[] {AUTOFILL_VIEW_EXITED});
dispatchDownAndUpKeyEvents(KeyEvent.KEYCODE_A);
waitForCallbackAndVerifyTypes(cnt,
new Integer[] {AUTOFILL_CANCEL, AUTOFILL_VIEW_ENTERED, AUTOFILL_VIEW_EXITED,
AUTOFILL_VIEW_ENTERED, AUTOFILL_VALUE_CHANGED});
} finally {
webServer.shutdown();
}
}
/**
* This test is verifying new session starts if frame change.
*/
@Test
@SmallTest
@Feature({"AndroidWebView"})
public void testSwitchFromIFrame() throws Throwable {
// we intentionally load main frame and iframe from the same URL and make both have the
// similar form, so the new session is triggered by frame change
TestWebServer webServer = TestWebServer.start();
final String data = "<html><head></head><body><form name='formname' id='formid'>"
+ "<input type='text' id='text1' name='username'"
+ " placeholder='placeholder@placeholder.com' autocomplete='username name'>"
+ "<input type='submit'></form>"
+ "<iframe id='myframe' src='" + FILE + "'></iframe>"
+ "</body></html>";
final String iframeData = "<html><head></head><body><form name='formname' id='formid'>"
+ "<input type='text' id='text1' name='username'"
+ " placeholder='placeholder@placeholder.com' autocomplete='username name' "
+ " autofocus>"
+ "<input type='submit'></form>"
+ "</body></html>";
try {
final String url = webServer.setResponse(FILE, data, null);
mContentsClient.setShouldInterceptRequestImpl(
new AwAutofillTestClient.ShouldInterceptRequestImpl() {
private int mCallCount;
@Override
public AwWebResourceResponse shouldInterceptRequest(
AwWebResourceRequest request) {
try {
if (url.equals(request.url)) {
// Only intercept the iframe's request.
if (mCallCount == 1) {
final String encoding = "UTF-8";
return new AwWebResourceResponse("text/html", encoding,
new ByteArrayInputStream(
iframeData.getBytes(encoding)));
}
mCallCount++;
}
return null;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
});
loadUrlSync(url);
// Trigger the autofill in iframe.
int count = clearEventQueueAndGetCallCount();
dispatchDownAndUpKeyEvents(KeyEvent.KEYCODE_A);
// Verify autofill session triggered.
count += waitForCallbackAndVerifyTypes(count,
new Integer[] {AUTOFILL_CANCEL, AUTOFILL_VIEW_ENTERED, AUTOFILL_VIEW_EXITED,
AUTOFILL_VIEW_ENTERED, AUTOFILL_VALUE_CHANGED});
// Verify focus is in iframe.
assertEquals("true",
executeJavaScriptAndWaitForResult(
"document.getElementById('myframe').contentDocument.hasFocus()"));
// Move focus to the main frame form.
clearChangedValues();
executeJavaScriptAndWaitForResult("document.getElementById('text1').select();");
dispatchDownAndUpKeyEvents(KeyEvent.KEYCODE_A);
// The new session starts because cancel() has been called.
waitForCallbackAndVerifyTypes(count,
new Integer[] {AUTOFILL_VIEW_EXITED, AUTOFILL_CANCEL, AUTOFILL_VIEW_ENTERED,
AUTOFILL_VIEW_EXITED, AUTOFILL_VIEW_ENTERED, AUTOFILL_VALUE_CHANGED});
ArrayList<Pair<Integer, AutofillValue>> values = getChangedValues();
assertEquals(1, values.size());
assertEquals("a", values.get(0).second.getTextValue());
// Verify focus isn't in iframe now.
assertEquals("false",
executeJavaScriptAndWaitForResult(
"document.getElementById('myframe').contentDocument.hasFocus()"));
} finally {
webServer.shutdown();
}
}
/**
* This test is verifying new session starts if frame change.
*/
@Test
@SmallTest
@Feature({"AndroidWebView"})
public void testTouchingPasswordFieldTriggerQuery() throws Throwable {
int cnt = 0;
TestWebServer webServer = TestWebServer.start();
final String data =
"<html><head></head><body><form action='a.html' name='formname' id='formid'>"
+ "<input type='password' id='passwordid' name='passwordname'"
+ "<input type='submit'>"
+ "</form></body></html>";
try {
final String url = webServer.setResponse(FILE, data, null);
loadUrlSync(url);
} finally {
webServer.shutdown();
DOMUtils.waitForNonZeroNodeBounds(mAwContents.getWebContents(), "passwordid");
// Note that we currently depend on keyboard app's behavior.
// TODO(changwan): mock out IME interaction.
Assert.assertTrue(
DOMUtils.clickNode(mTestContainerView.getWebContents(), "passwordid"));
cnt += waitForCallbackAndVerifyTypes(
cnt, new Integer[] {AUTOFILL_CANCEL, AUTOFILL_VIEW_ENTERED});
}
}
/**
* This test is verifying the session is still alive after navigation.
*/
@Test
@SmallTest
@Feature({"AndroidWebView"})
public void testSessionAliveAfterNavigation() throws Throwable {
int cnt = 0;
TestWebServer webServer = TestWebServer.start();
final String data = "<!DOCTYPE html>"
+ "<html>"
+ "<body>"
+ "<form action='a.html' name='formname' id='formid'>"
+ "<input type='text' id='text1' name='username'"
+ " placeholder='placeholder@placeholder.com' autocomplete='username name'>"
+ "<input type='password' id='passwordid' name='passwordname'>"
+ "</form>"
+ "</body>"
+ "</html>";
final String success = "<!DOCTYPE html>"
+ "<html>"
+ "<body>"
+ "</body>"
+ "</html>";
try {
webServer.setResponse("/success.html", success, null);
final String url = webServer.setResponse(FILE, data, null);
loadUrlSync(url);
executeJavaScriptAndWaitForResult("document.getElementById('text1').select();");
dispatchDownAndUpKeyEvents(KeyEvent.KEYCODE_A);
// Note that we currently call ENTER/EXIT one more time.
cnt += waitForCallbackAndVerifyTypes(cnt,
new Integer[] {AUTOFILL_CANCEL, AUTOFILL_VIEW_ENTERED, AUTOFILL_VIEW_EXITED,
AUTOFILL_VIEW_ENTERED, AUTOFILL_VALUE_CHANGED});
executeJavaScriptAndWaitForResult("window.location.href = 'success.html'; ");
waitForCallbackAndVerifyTypes(cnt,
new Integer[] {
AUTOFILL_VALUE_CHANGED, AUTOFILL_VALUE_CHANGED, AUTOFILL_COMMIT});
assertEquals(SubmissionSource.PROBABLY_FORM_SUBMITTED, mSubmissionSource);
} finally {
webServer.shutdown();
}
}
/**
* This test is verifying there is no callback if there is no form change between two
* navigations.
*/
@Test
@SmallTest
@Feature({"AndroidWebView"})
public void testNoSubmissionWithoutFillingForm() throws Throwable {
int cnt = 0;
TestWebServer webServer = TestWebServer.start();
final String data = "<!DOCTYPE html>"
+ "<html>"
+ "<body>"
+ "<form action='a.html' name='formname' id='formid'>"
+ "<input type='text' id='text1' name='username'"
+ " placeholder='placeholder@placeholder.com' autocomplete='username name'>"
+ "<input type='password' id='passwordid' name='passwordname'>"
+ "</form>"
+ "</body>"
+ "</html>";
final String success = "<!DOCTYPE html>"
+ "<html>"
+ "<body>"
+ "</body>"
+ "</html>";
try {
final String successUrl = webServer.setResponse("/success.html", success, null);
final String url = webServer.setResponse(FILE, data, null);
loadUrlSync(url);
executeJavaScriptAndWaitForResult("window.location.href = 'success.html'; ");
// There is no callback. AUTOFILL_CANCEL shouldn't be invoked.
assertEquals(0, getCallbackCount());
} finally {
webServer.shutdown();
}
}
@Test
@SmallTest
@Feature({"AndroidWebView"})
public void testSelectControlChangeNotification() throws Throwable {
int cnt = 0;
TestWebServer webServer = TestWebServer.start();
final String data = "<!DOCTYPE html>"
+ "<html>"
+ "<body>"
+ "<form action='a.html' name='formname' id='formid'>"
+ "<input type='text' id='text1' name='username'>"
+ "<select id='color' autofocus><option value='red'>red</option><option "
+ "value='blue' id='blue'>blue</option></select>"
+ "</form>"
+ "</body>"
+ "</html>";
try {
final String url = webServer.setResponse(FILE, data, null);
loadUrlSync(url);
executeJavaScriptAndWaitForResult("document.getElementById('text1').select();");
dispatchDownAndUpKeyEvents(KeyEvent.KEYCODE_A);
cnt += waitForCallbackAndVerifyTypes(cnt,
new Integer[] {AUTOFILL_CANCEL, AUTOFILL_VIEW_ENTERED, AUTOFILL_VIEW_EXITED,
AUTOFILL_VIEW_ENTERED, AUTOFILL_VALUE_CHANGED});
clearChangedValues();
executeJavaScriptAndWaitForResult("document.getElementById('color').focus();");
dispatchDownAndUpKeyEvents(KeyEvent.KEYCODE_DPAD_CENTER);
// Use key B to select 'blue'.
dispatchDownAndUpKeyEvents(KeyEvent.KEYCODE_B);
cnt += waitForCallbackAndVerifyTypes(cnt,
new Integer[] {
AUTOFILL_VIEW_EXITED, AUTOFILL_VIEW_ENTERED, AUTOFILL_VALUE_CHANGED});
ArrayList<Pair<Integer, AutofillValue>> values = getChangedValues();
assertEquals(1, values.size());
assertTrue(values.get(0).second.isList());
assertEquals(1, values.get(0).second.getListValue());
} finally {
webServer.shutdown();
}
}
@Test
@SmallTest
@Feature({"AndroidWebView"})
public void testSelectControlChangeStartAutofillSession() throws Throwable {
int cnt = 0;
TestWebServer webServer = TestWebServer.start();
final String data = "<!DOCTYPE html>"
+ "<html>"
+ "<body>"
+ "<form action='a.html' name='formname' id='formid'>"
+ "<input type='text' id='text1' name='username'>"
+ "<select id='color' autofocus><option value='red'>red</option><option "
+ "value='blue' id='blue'>blue</option></select>"
+ "</form>"
+ "</body>"
+ "</html>";
try {
final String url = webServer.setResponse(FILE, data, null);
loadUrlSync(url);
// Change select control first shall start autofill session.
dispatchDownAndUpKeyEvents(KeyEvent.KEYCODE_DPAD_CENTER);
// Use key B to select 'blue'.
dispatchDownAndUpKeyEvents(KeyEvent.KEYCODE_B);
cnt += waitForCallbackAndVerifyTypes(cnt,
new Integer[] {AUTOFILL_CANCEL, AUTOFILL_VIEW_ENTERED, AUTOFILL_VIEW_EXITED,
AUTOFILL_VIEW_ENTERED, AUTOFILL_VALUE_CHANGED});
ArrayList<Pair<Integer, AutofillValue>> values = getChangedValues();
assertEquals(1, values.size());
assertTrue(values.get(0).second.isList());
assertEquals(1, values.get(0).second.getListValue());
} finally {
webServer.shutdown();
}
}
@Test
@SmallTest
@Feature({"AndroidWebView"})
public void testUserInitiatedJavascriptSelectControlChangeNotification() throws Throwable {
int cnt = 0;
TestWebServer webServer = TestWebServer.start();
final String data = "<!DOCTYPE html>"
+ "<html>"
+ "<body>"
+ "<script>"
+ "function myFunction() {"
+ " document.getElementById('color').value = 'blue';"
+ "}"
+ "</script>"
+ "<form action='a.html' name='formname' id='formid'>"
+ "<button onclick='myFunction();' autofocus>button </button>"
+ "<select id='color' autofocus><option value='red'>red</option><option "
+ "value='blue' id='blue'>blue</option></select>"
+ "</form>"
+ "</body>"
+ "</html>";
try {
final String url = webServer.setResponse(FILE, data, null);
loadUrlSync(url);
// Change select control first shall start autofill session.
dispatchDownAndUpKeyEvents(KeyEvent.KEYCODE_DPAD_CENTER);
cnt += waitForCallbackAndVerifyTypes(cnt,
new Integer[] {AUTOFILL_CANCEL, AUTOFILL_VIEW_ENTERED, AUTOFILL_VIEW_EXITED,
AUTOFILL_VIEW_ENTERED, AUTOFILL_VALUE_CHANGED});
ArrayList<Pair<Integer, AutofillValue>> values = getChangedValues();
assertEquals(1, values.size());
assertTrue(values.get(0).second.isList());
assertEquals(1, values.get(0).second.getListValue());
} finally {
webServer.shutdown();
}
}
@Test
@SmallTest
@Feature({"AndroidWebView"})
public void testJavascriptNotTriggerSelectControlChangeNotification() throws Throwable {
int cnt = 0;
TestWebServer webServer = TestWebServer.start();
final String data = "<!DOCTYPE html>"
+ "<html>"
+ "<body onload='myFunction();'>"
+ "<script>"
+ "function myFunction() {"
+ " document.getElementById('color').value = 'blue';"
+ "}"
+ "</script>"
+ "<form action='a.html' name='formname' id='formid'>"
+ "<button onclick='myFunction();' autofocus>button </button>"
+ "<select id='color' autofocus><option value='red'>red</option><option "
+ "value='blue' id='blue'>blue</option></select>"
+ "</form>"
+ "</body>"
+ "</html>";
try {
final String url = webServer.setResponse(FILE, data, null);
loadUrlSync(url);
waitForCallbackAndVerifyTypes(cnt, new Integer[] {});
} finally {
webServer.shutdown();
}
}
@Test
@SmallTest
@Feature({"AndroidWebView"})
public void testUaAutofillHints() throws Throwable {
TestWebServer webServer = TestWebServer.start();
final String data = "<html><head></head><body><form action='a.html' name='formname'>"
+ "<label for=\"frmAddressB\">Address</label>"
+ "<input name=\"bill-address\" id=\"frmAddressB\">"
+ "<label for=\"frmCityB\">City</label>"
+ "<input name=\"bill-city\" id=\"frmCityB\">"
+ "<label for=\"frmStateB\">State</label>"
+ "<input name=\"bill-state\" id=\"frmStateB\">"
+ "<label for=\"frmZipB\">Zip</label>"
+ "<input name=\"bill-zip\" id=\"frmZipB\">"
+ "<input type='checkbox' id='checkbox1' name='showpassword'>"
+ "<label for=\"frmCountryB\">Country</label>"
+ "<input name=\"bill-country\" id=\"frmCountryB\">"
+ "<input type='submit'>"
+ "</form></body></html>";
final int totalControls = 6;
try {
int cnt = 0;
final String url = webServer.setResponse(FILE, data, null);
loadUrlSync(url);
executeJavaScriptAndWaitForResult("document.getElementById('frmAddressB').select();");
dispatchDownAndUpKeyEvents(KeyEvent.KEYCODE_A);
// Note that we currently call ENTER/EXIT one more time.
cnt += waitForCallbackAndVerifyTypes(cnt,
new Integer[] {AUTOFILL_CANCEL, AUTOFILL_VIEW_ENTERED, AUTOFILL_VIEW_EXITED,
AUTOFILL_VIEW_ENTERED, AUTOFILL_VALUE_CHANGED});
invokeOnProvideAutoFillVirtualStructure();
TestViewStructure viewStructure = mTestValues.testViewStructure;
assertNotNull(viewStructure);
assertEquals(totalControls, viewStructure.getChildCount());
TestViewStructure child0 = viewStructure.getChild(0);
TestViewStructure.AwHtmlInfo htmlInfo0 = child0.getHtmlInfo();
assertEquals("ADDRESS_HOME_LINE1", htmlInfo0.getAttribute("ua-autofill-hints"));
TestViewStructure child1 = viewStructure.getChild(1);
TestViewStructure.AwHtmlInfo htmlInfo1 = child1.getHtmlInfo();
assertEquals("ADDRESS_HOME_CITY", htmlInfo1.getAttribute("ua-autofill-hints"));
TestViewStructure child2 = viewStructure.getChild(2);
TestViewStructure.AwHtmlInfo htmlInfo2 = child2.getHtmlInfo();
assertEquals("ADDRESS_HOME_STATE", htmlInfo2.getAttribute("ua-autofill-hints"));
TestViewStructure child3 = viewStructure.getChild(3);
TestViewStructure.AwHtmlInfo htmlInfo3 = child3.getHtmlInfo();
assertEquals("ADDRESS_HOME_ZIP", htmlInfo3.getAttribute("ua-autofill-hints"));
TestViewStructure child4 = viewStructure.getChild(4);
TestViewStructure.AwHtmlInfo htmlInfo4 = child4.getHtmlInfo();
assertNull(htmlInfo4.getAttribute("ua-autofill-hints"));
TestViewStructure child5 = viewStructure.getChild(5);
TestViewStructure.AwHtmlInfo htmlInfo5 = child5.getHtmlInfo();
assertEquals("ADDRESS_HOME_COUNTRY", htmlInfo5.getAttribute("ua-autofill-hints"));
} finally {
webServer.shutdown();
}
}
@Test
@SmallTest
@Feature({"AndroidWebView"})
public void testUMAUserSelectSuggestionUserChangeFormFormSubmitted() throws Throwable {
TestWebServer webServer = TestWebServer.start();
try {
mUMATestHelper.triggerAutofill(webServer);
invokeOnProvideAutoFillVirtualStructure();
invokeOnInputUIShown();
mUMATestHelper.simulateUserSelectSuggestion();
mUMATestHelper.simulateUserChangeField();
mUMATestHelper.submitForm();
assertEquals(AwAutofillUMA.USER_SELECT_SUGGESTION_USER_CHANGE_FORM_FORM_SUBMITTED,
mUMATestHelper.getSessionValue());
assertEquals(AwAutofillUMA.FORM_SUBMISSION, mUMATestHelper.getSubmissionSourceValue());
} finally {
webServer.shutdown();
}
}
@Test
@SmallTest
@Feature({"AndroidWebView"})
public void testUMAUserSelectSuggestionUserChangeFormNoFormSubmitted() throws Throwable {
TestWebServer webServer = TestWebServer.start();
try {
mUMATestHelper.triggerAutofill(webServer);
invokeOnProvideAutoFillVirtualStructure();
invokeOnInputUIShown();
mUMATestHelper.simulateUserSelectSuggestion();
mUMATestHelper.simulateUserChangeField();
mUMATestHelper.startNewSession();
assertEquals(AwAutofillUMA.USER_SELECT_SUGGESTION_USER_CHANGE_FORM_NO_FORM_SUBMITTED,
mUMATestHelper.getSessionValue());
assertEquals(AwAutofillSessionUMATestHelper.NO_FORM_SUBMISSION,
mUMATestHelper.getSubmissionSourceValue());
} finally {
webServer.shutdown();
}
}
@Test
@SmallTest
@Feature({"AndroidWebView"})
public void testUMAUserSelectNotSuggestionUserChangeFormNoFormSubmitted() throws Throwable {
TestWebServer webServer = TestWebServer.start();
try {
int count = mUMATestHelper.getHistogramSampleCount(
AwAutofillUMA.UMA_AUTOFILL_WEBVIEW_SUGGESTION_TIME);
mUMATestHelper.triggerAutofill(webServer);
invokeOnProvideAutoFillVirtualStructure();
invokeOnInputUIShown();
mUMATestHelper.simulateUserChangeField();
mUMATestHelper.startNewSession();
assertEquals(
AwAutofillUMA.USER_NOT_SELECT_SUGGESTION_USER_CHANGE_FORM_NO_FORM_SUBMITTED,
mUMATestHelper.getSessionValue());
assertEquals(AwAutofillSessionUMATestHelper.NO_FORM_SUBMISSION,
mUMATestHelper.getSubmissionSourceValue());
assertEquals(count + 1,
mUMATestHelper.getHistogramSampleCount(
AwAutofillUMA.UMA_AUTOFILL_WEBVIEW_SUGGESTION_TIME));
} finally {
webServer.shutdown();
}
}
@Test
@SmallTest
@Feature({"AndroidWebView"})
public void testUMAUserNotSelectSuggestionUserChangeFormFormSubmitted() throws Throwable {
TestWebServer webServer = TestWebServer.start();
try {
mUMATestHelper.triggerAutofill(webServer);
invokeOnProvideAutoFillVirtualStructure();
invokeOnInputUIShown();
mUMATestHelper.simulateUserChangeField();
mUMATestHelper.submitForm();
assertEquals(AwAutofillUMA.USER_NOT_SELECT_SUGGESTION_USER_CHANGE_FORM_FORM_SUBMITTED,
mUMATestHelper.getSessionValue());
assertEquals(AwAutofillUMA.FORM_SUBMISSION, mUMATestHelper.getSubmissionSourceValue());
} finally {
webServer.shutdown();
}
}
@Test
@SmallTest
@Feature({"AndroidWebView"})
public void testUMANoSuggestionUserChangeFormNoFormSubmitted() throws Throwable {
TestWebServer webServer = TestWebServer.start();
try {
mUMATestHelper.triggerAutofill(webServer);
invokeOnProvideAutoFillVirtualStructure();
mUMATestHelper.simulateUserChangeField();
mUMATestHelper.startNewSession();
assertEquals(AwAutofillUMA.NO_SUGGESTION_USER_CHANGE_FORM_NO_FORM_SUBMITTED,
mUMATestHelper.getSessionValue());
assertEquals(AwAutofillSessionUMATestHelper.NO_FORM_SUBMISSION,
mUMATestHelper.getSubmissionSourceValue());
mUMATestHelper.verifyUserChangedNonAutofilledField();
} finally {
webServer.shutdown();
}
}
@Test
@SmallTest
@Feature({"AndroidWebView"})
public void testUMANoSuggestionUserChangeFormFormSubmitted() throws Throwable {
TestWebServer webServer = TestWebServer.start();
try {
mUMATestHelper.triggerAutofill(webServer);
invokeOnProvideAutoFillVirtualStructure();
mUMATestHelper.simulateUserChangeField();
mUMATestHelper.submitForm();
assertEquals(AwAutofillUMA.NO_SUGGESTION_USER_CHANGE_FORM_FORM_SUBMITTED,
mUMATestHelper.getSessionValue());
assertEquals(AwAutofillUMA.FORM_SUBMISSION, mUMATestHelper.getSubmissionSourceValue());
mUMATestHelper.verifyUserChangedNonAutofilledField();
} finally {
webServer.shutdown();
}
}
@Test
@SmallTest
@Feature({"AndroidWebView"})
public void testUMAUserSelectSuggestionUserNotChangeFormFormSubmitted() throws Throwable {
TestWebServer webServer = TestWebServer.start();
try {
mUMATestHelper.triggerAutofill(webServer);
invokeOnProvideAutoFillVirtualStructure();
invokeOnInputUIShown();
mUMATestHelper.simulateUserSelectSuggestion();
mUMATestHelper.submitForm();
assertEquals(AwAutofillUMA.USER_SELECT_SUGGESTION_USER_NOT_CHANGE_FORM_FORM_SUBMITTED,
mUMATestHelper.getSessionValue());
assertEquals(AwAutofillUMA.FORM_SUBMISSION, mUMATestHelper.getSubmissionSourceValue());
mUMATestHelper.verifyUserDidntChangeForm();
} finally {
webServer.shutdown();
}
}
@Test
@SmallTest
@Feature({"AndroidWebView"})
public void testUMAUserSelectSuggestionUserNotChangeFormNoFormSubmitted() throws Throwable {
TestWebServer webServer = TestWebServer.start();
try {
mUMATestHelper.triggerAutofill(webServer);
invokeOnProvideAutoFillVirtualStructure();
invokeOnInputUIShown();
mUMATestHelper.simulateUserSelectSuggestion();
mUMATestHelper.startNewSession();
assertEquals(
AwAutofillUMA.USER_SELECT_SUGGESTION_USER_NOT_CHANGE_FORM_NO_FORM_SUBMITTED,
mUMATestHelper.getSessionValue());
assertEquals(AwAutofillSessionUMATestHelper.NO_FORM_SUBMISSION,
mUMATestHelper.getSubmissionSourceValue());
mUMATestHelper.verifyUserDidntChangeForm();
} finally {
webServer.shutdown();
}
}
@Test
@SmallTest
@Feature({"AndroidWebView"})
public void testUMAUserSelectNotSuggestionUserNotChangeFormNoFormSubmitted() throws Throwable {
TestWebServer webServer = TestWebServer.start();
try {
mUMATestHelper.triggerAutofill(webServer);
invokeOnProvideAutoFillVirtualStructure();
invokeOnInputUIShown();
mUMATestHelper.startNewSession();
assertEquals(
AwAutofillUMA.USER_NOT_SELECT_SUGGESTION_USER_NOT_CHANGE_FORM_NO_FORM_SUBMITTED,
mUMATestHelper.getSessionValue());
assertEquals(AwAutofillSessionUMATestHelper.NO_FORM_SUBMISSION,
mUMATestHelper.getSubmissionSourceValue());
mUMATestHelper.verifyUserDidntChangeForm();
} finally {
webServer.shutdown();
}
}
@Test
@SmallTest
@Feature({"AndroidWebView"})
public void testUMAUserNotSelectSuggestionUserNotChangeFormFormSubmitted() throws Throwable {
TestWebServer webServer = TestWebServer.start();
try {
mUMATestHelper.triggerAutofill(webServer);
invokeOnProvideAutoFillVirtualStructure();
invokeOnInputUIShown();
mUMATestHelper.submitForm();
assertEquals(
AwAutofillUMA.USER_NOT_SELECT_SUGGESTION_USER_NOT_CHANGE_FORM_FORM_SUBMITTED,
mUMATestHelper.getSessionValue());
assertEquals(AwAutofillUMA.FORM_SUBMISSION, mUMATestHelper.getSubmissionSourceValue());
mUMATestHelper.verifyUserDidntChangeForm();
} finally {
webServer.shutdown();
}
}
@Test
@SmallTest
@Feature({"AndroidWebView"})
public void testUMANoSuggestionUserNotChangeFormNoFormSubmitted() throws Throwable {
TestWebServer webServer = TestWebServer.start();
try {
mUMATestHelper.triggerAutofill(webServer);
invokeOnProvideAutoFillVirtualStructure();
mUMATestHelper.startNewSession();
assertEquals(AwAutofillUMA.NO_SUGGESTION_USER_NOT_CHANGE_FORM_NO_FORM_SUBMITTED,
mUMATestHelper.getSessionValue());
assertEquals(AwAutofillSessionUMATestHelper.NO_FORM_SUBMISSION,
mUMATestHelper.getSubmissionSourceValue());
mUMATestHelper.verifyUserDidntChangeForm();
} finally {
webServer.shutdown();
}
}
@Test
@SmallTest
@Feature({"AndroidWebView"})
public void testUMANoSuggestionUserNotChangeFormFormSubmitted() throws Throwable {
TestWebServer webServer = TestWebServer.start();
try {
mUMATestHelper.triggerAutofill(webServer);
invokeOnProvideAutoFillVirtualStructure();
mUMATestHelper.submitForm();
assertEquals(AwAutofillUMA.NO_SUGGESTION_USER_NOT_CHANGE_FORM_FORM_SUBMITTED,
mUMATestHelper.getSessionValue());
assertEquals(AwAutofillUMA.FORM_SUBMISSION, mUMATestHelper.getSubmissionSourceValue());
mUMATestHelper.verifyUserDidntChangeForm();
} finally {
webServer.shutdown();
}
}
@Test
@SmallTest
@Feature({"AndroidWebView"})
public void testUMANoCallbackFromFramework() throws Throwable {
TestWebServer webServer = TestWebServer.start();
try {
mUMATestHelper.triggerAutofill(webServer);
mUMATestHelper.startNewSession();
assertEquals(
AwAutofillUMA.NO_CALLBACK_FORM_FRAMEWORK, mUMATestHelper.getSessionValue());
assertEquals(AwAutofillSessionUMATestHelper.NO_FORM_SUBMISSION,
mUMATestHelper.getSubmissionSourceValue());
} finally {
webServer.shutdown();
}
}
@Test
@SmallTest
@Feature({"AndroidWebView"})
public void testUMAAutofillDisabled() throws Throwable {
mTestAwAutofillManager.setDisabled();
TestWebServer webServer = TestWebServer.start();
try {
mUMATestHelper.triggerAutofill(webServer);
mUMATestHelper.verifyAutofillDisabled();
assertEquals(AwAutofillSessionUMATestHelper.NO_FORM_SUBMISSION,
mUMATestHelper.getSubmissionSourceValue());
} finally {
webServer.shutdown();
}
}
@Test
@SmallTest
@Feature({"AndroidWebView"})
public void testUMAAutofillEnabled() throws Throwable {
TestWebServer webServer = TestWebServer.start();
try {
mUMATestHelper.triggerAutofill(webServer);
mUMATestHelper.verifyAutofillEnabled();
assertEquals(AwAutofillSessionUMATestHelper.NO_FORM_SUBMISSION,
mUMATestHelper.getSubmissionSourceValue());
} finally {
webServer.shutdown();
}
}
@Test
@SmallTest
@Feature({"AndroidWebView"})
public void testUMAUserChangeAutofilledField() throws Throwable {
TestWebServer webServer = TestWebServer.start();
try {
mUMATestHelper.triggerAutofill(webServer);
invokeOnProvideAutoFillVirtualStructure();
invokeOnInputUIShown();
mUMATestHelper.simulateUserSelectSuggestion();
mUMATestHelper.simulateUserChangeAutofilledField();
mUMATestHelper.submitForm();
assertEquals(AwAutofillUMA.USER_SELECT_SUGGESTION_USER_CHANGE_FORM_FORM_SUBMITTED,
mUMATestHelper.getSessionValue());
assertEquals(AwAutofillUMA.FORM_SUBMISSION, mUMATestHelper.getSubmissionSourceValue());
mUMATestHelper.verifyUserChangedAutofilledField();
} finally {
webServer.shutdown();
}
}
@Test
@SmallTest
@Feature({"AndroidWebView"})
public void testUMAAutofillCreatedByActivityContext() throws Throwable {
mUMATestHelper.verifyWebViewCreatedByActivityContext();
}
private void loadUrlSync(String url) throws Exception {
mRule.loadUrlSync(
mTestContainerView.getAwContents(), mContentsClient.getOnPageFinishedHelper(), url);
}
private String executeJavaScriptAndWaitForResult(String code) throws Throwable {
return mRule.executeJavaScriptAndWaitForResult(
mTestContainerView.getAwContents(), mContentsClient, code);
}
private ArrayList<Pair<Integer, AutofillValue>> getChangedValues() {
return mTestValues.changedValues;
}
private void clearChangedValues() {
if (mTestValues.changedValues != null) mTestValues.changedValues.clear();
}
private void invokeOnProvideAutoFillVirtualStructure() {
TestThreadUtils.runOnUiThreadBlocking(() -> {
mTestValues.testViewStructure = new TestViewStructure();
mAwContents.onProvideAutoFillVirtualStructure(mTestValues.testViewStructure, 1);
});
}
private void invokeAutofill(SparseArray<AutofillValue> values) {
TestThreadUtils.runOnUiThreadBlocking(() -> mAwContents.autofill(values));
}
private void invokeOnInputUIShown() {
TestThreadUtils.runOnUiThreadBlocking(() -> mTestAwAutofillManager.notifyInputUIChange());
}
private int getCallbackCount() {
return mCallbackHelper.getCallCount();
}
private int clearEventQueueAndGetCallCount() {
mEventQueue.clear();
return mCallbackHelper.getCallCount();
}
/**
* Wait for expected callbacks to be called, and verify the types.
*
* @param currentCallCount The current call count to start from.
* @param expectedEventArray The callback types that need to be verified.
* @return The number of new callbacks since currentCallCount. This should be same as the length
* of expectedEventArray.
* @throws InterruptedException
* @throws TimeoutException
*/
private int waitForCallbackAndVerifyTypes(int currentCallCount, Integer[] expectedEventArray)
throws InterruptedException, TimeoutException {
Integer[] adjustedEventArray;
// Didn't call cancel after Android P.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
ArrayList<Integer> adjusted = new ArrayList<Integer>();
for (Integer event : expectedEventArray) {
if (event != AUTOFILL_CANCEL) adjusted.add(event);
}
adjustedEventArray = new Integer[adjusted.size()];
adjusted.toArray(adjustedEventArray);
} else {
adjustedEventArray = expectedEventArray;
}
try {
// Check against the call count to avoid missing out a callback in between waits, while
// exposing it so that the test can control where the call count starts.
mCallbackHelper.waitForCallback(currentCallCount, adjustedEventArray.length);
Object[] objectArray = mEventQueue.toArray();
mEventQueue.clear();
Integer[] resultArray = Arrays.copyOf(objectArray, objectArray.length, Integer[].class);
Assert.assertArrayEquals("Expect: " + Arrays.toString(adjustedEventArray)
+ " Result: " + Arrays.toString(resultArray),
adjustedEventArray, resultArray);
return adjustedEventArray.length;
} catch (TimeoutException e) {
Object[] objectArray = mEventQueue.toArray();
Integer[] resultArray = Arrays.copyOf(objectArray, objectArray.length, Integer[].class);
Assert.assertArrayEquals("Expect:" + Arrays.toString(adjustedEventArray)
+ " Result:" + Arrays.toString(resultArray),
adjustedEventArray, resultArray);
throw e;
}
}
private void dispatchDownAndUpKeyEvents(final int code) throws Throwable {
dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, code));
dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, code));
}
private boolean dispatchKeyEvent(final KeyEvent event) throws Throwable {
return TestThreadUtils.runOnUiThreadBlocking(new Callable<Boolean>() {
@Override
public Boolean call() {
return mTestContainerView.dispatchKeyEvent(event);
}
});
}
}