blob: 8cd9a670b6487af8c5717ff11177e4706a1b40c2 [file] [log] [blame]
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.autofillservice.cts;
import static android.autofillservice.cts.CannedFillResponse.NO_RESPONSE;
import static android.autofillservice.cts.Helper.ID_PASSWORD;
import static android.autofillservice.cts.Helper.ID_PASSWORD_LABEL;
import static android.autofillservice.cts.Helper.ID_USERNAME;
import static android.autofillservice.cts.Helper.ID_USERNAME_LABEL;
import static android.autofillservice.cts.Helper.assertTextAndValue;
import static android.autofillservice.cts.Helper.assertTextIsSanitized;
import static android.autofillservice.cts.Helper.assertTextOnly;
import static android.autofillservice.cts.Helper.dumpStructure;
import static android.autofillservice.cts.Helper.findNodeByResourceId;
import static android.autofillservice.cts.VirtualContainerView.ID_URL_BAR;
import static android.autofillservice.cts.VirtualContainerView.LABEL_CLASS;
import static android.autofillservice.cts.VirtualContainerView.TEXT_CLASS;
import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static org.junit.Assume.assumeTrue;
import android.app.assist.AssistStructure.ViewNode;
import android.autofillservice.cts.CannedFillResponse.CannedDataset;
import android.autofillservice.cts.InstrumentedAutoFillService.FillRequest;
import android.autofillservice.cts.InstrumentedAutoFillService.SaveRequest;
import android.autofillservice.cts.VirtualContainerView.Line;
import android.autofillservice.cts.VirtualContainerView.VisibilityIntegrationMode;
import android.graphics.Rect;
import android.platform.test.annotations.AppModeFull;
import android.service.autofill.SaveInfo;
import android.support.test.uiautomator.UiObject2;
import android.text.InputType;
import android.view.ViewGroup;
import android.view.autofill.AutofillManager;
import org.junit.Ignore;
import org.junit.Test;
import java.util.concurrent.TimeoutException;
/**
* Test case for an activity containing virtual children, either using the explicit Autofill APIs
* or Compat mode.
*/
public class VirtualContainerActivityTest
extends AutoFillServiceTestCase.AutoActivityLaunch<VirtualContainerActivity> {
// TODO(b/74256300): remove when fixed it :-)
private static final boolean BUG_74256300_FIXED = false;
private final boolean mCompatMode;
private AutofillActivityTestRule<VirtualContainerActivity> mActivityRule;
protected VirtualContainerActivity mActivity;
public VirtualContainerActivityTest() {
this(false);
}
protected VirtualContainerActivityTest(boolean compatMode) {
mCompatMode = compatMode;
}
/**
* Hook for subclass to customize test before activity is created.
*/
protected void preActivityCreated() {}
/**
* Hook for subclass to customize activity after it's launched.
*/
protected void postActivityLaunched() {}
@Override
protected AutofillActivityTestRule<VirtualContainerActivity> getActivityRule() {
if (mActivityRule == null) {
mActivityRule = new AutofillActivityTestRule<VirtualContainerActivity>(
VirtualContainerActivity.class) {
@Override
protected void beforeActivityLaunched() {
preActivityCreated();
}
@Override
protected void afterActivityLaunched() {
mActivity = getActivity();
postActivityLaunched();
}
};
}
return mActivityRule;
}
@Test
public void testAutofillSync() throws Exception {
autofillTest(true);
}
@Test
@AppModeFull(reason = "testAutofillSync() is enough")
public void testAutofillAsync() throws Exception {
skipTestOnCompatMode();
autofillTest(false);
}
@Test
public void testAutofill_appContext() throws Exception {
mActivity.mCustomView.setAutofillManager(mActivity.getApplicationContext());
autofillTest(true);
// Sanity check to make sure autofill is enabled in the application context
assertThat(mActivity.getApplicationContext().getSystemService(AutofillManager.class)
.isEnabled()).isTrue();
}
/**
* Focus to username and expect window event
*/
void focusToUsername() throws TimeoutException {
mUiBot.waitForWindowChange(() -> mActivity.mUsername.changeFocus(true));
}
/**
* Focus to username and expect no autofill window event
*/
void focusToUsernameExpectNoWindowEvent() throws Throwable {
// TODO: should use waitForWindowChange() if we can filter out event of app Activity itself.
mActivityRule.runOnUiThread(() -> mActivity.mUsername.changeFocus(true));
}
/**
* Focus to password and expect window event
*/
void focusToPassword() throws TimeoutException {
mUiBot.waitForWindowChange(() -> mActivity.mPassword.changeFocus(true));
}
/**
* Focus to password and expect no autofill window event
*/
void focusToPasswordExpectNoWindowEvent() throws Throwable {
// TODO should use waitForWindowChange() if we can filter out event of app Activity itself.
mActivityRule.runOnUiThread(() -> mActivity.mPassword.changeFocus(true));
}
/**
* Tests autofilling the virtual views, using the sync / async version of ViewStructure.addChild
*/
private void autofillTest(boolean sync) throws Exception {
// Set service.
enableService();
// Set expectations.
sReplier.addResponse(new CannedDataset.Builder()
.setField(ID_USERNAME, "dude", createPresentation("DUDE"))
.setField(ID_PASSWORD, "sweet", createPresentation("SWEET"))
.build());
mActivity.expectAutoFill("dude", "sweet");
mActivity.mCustomView.setSync(sync);
// Trigger auto-fill.
focusToUsername();
assertDatasetShown(mActivity.mUsername, "DUDE");
// Play around with focus to make sure picker is properly drawn.
if (BUG_74256300_FIXED || !mCompatMode) {
focusToPassword();
assertDatasetShown(mActivity.mPassword, "SWEET");
focusToUsername();
assertDatasetShown(mActivity.mUsername, "DUDE");
}
// Make sure input was sanitized.
final FillRequest request = sReplier.getNextFillRequest();
final ViewNode urlBar = findNodeByResourceId(request.structure, ID_URL_BAR);
final ViewNode usernameLabel = findNodeByResourceId(request.structure, ID_USERNAME_LABEL);
final ViewNode username = findNodeByResourceId(request.structure, ID_USERNAME);
final ViewNode passwordLabel = findNodeByResourceId(request.structure, ID_PASSWORD_LABEL);
final ViewNode password = findNodeByResourceId(request.structure, ID_PASSWORD);
assertUrlBarIsSanitized(urlBar);
assertTextIsSanitized(username);
assertTextIsSanitized(password);
assertLabel(usernameLabel, "Username");
assertLabel(passwordLabel, "Password");
assertThat(usernameLabel.getClassName()).isEqualTo(LABEL_CLASS);
assertThat(username.getClassName()).isEqualTo(TEXT_CLASS);
assertThat(passwordLabel.getClassName()).isEqualTo(LABEL_CLASS);
assertThat(password.getClassName()).isEqualTo(TEXT_CLASS);
assertThat(username.getIdEntry()).isEqualTo(ID_USERNAME);
assertThat(password.getIdEntry()).isEqualTo(ID_PASSWORD);
assertThat(username.getInputType())
.isEqualTo(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_NORMAL);
assertThat(usernameLabel.getInputType()).isEqualTo(0);
assertThat(password.getInputType())
.isEqualTo(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
assertThat(passwordLabel.getInputType()).isEqualTo(0);
final String[] autofillHints = username.getAutofillHints();
final boolean hasCompatModeFlag = (request.flags
& android.service.autofill.FillRequest.FLAG_COMPATIBILITY_MODE_REQUEST) != 0;
if (mCompatMode) {
assertThat(hasCompatModeFlag).isTrue();
assertThat(autofillHints).isNull();
assertThat(username.getHtmlInfo()).isNull();
assertThat(password.getHtmlInfo()).isNull();
} else {
assertThat(hasCompatModeFlag).isFalse();
// Make sure order is preserved and dupes not removed.
assertThat(autofillHints).asList()
.containsExactly("c", "a", "a", "b", "a", "a")
.inOrder();
try {
VirtualContainerView.assertHtmlInfo(username);
VirtualContainerView.assertHtmlInfo(password);
} catch (AssertionError | RuntimeException e) {
dumpStructure("HtmlInfo failed", request.structure);
throw e;
}
}
// Make sure initial focus was properly set.
assertWithMessage("Username node is not focused").that(username.isFocused()).isTrue();
assertWithMessage("Password node is focused").that(password.isFocused()).isFalse();
// Auto-fill it.
mUiBot.selectDataset("DUDE");
// Check the results.
mActivity.assertAutoFilled();
}
@Test
@AppModeFull(reason = "testAutofillSync() is enough")
public void testAutofillTwoDatasets() throws Exception {
// Set service.
enableService();
// Set expectations.
sReplier.addResponse(new CannedFillResponse.Builder()
.addDataset(new CannedDataset.Builder()
.setField(ID_USERNAME, "dude")
.setField(ID_PASSWORD, "sweet")
.setPresentation(createPresentation("The Dude"))
.build())
.addDataset(new CannedDataset.Builder()
.setField(ID_USERNAME, "DUDE")
.setField(ID_PASSWORD, "SWEET")
.setPresentation(createPresentation("THE DUDE"))
.build())
.build());
mActivity.expectAutoFill("DUDE", "SWEET");
// Trigger auto-fill.
focusToUsername();
sReplier.getNextFillRequest();
assertDatasetShown(mActivity.mUsername, "The Dude", "THE DUDE");
// Play around with focus to make sure picker is properly drawn.
if (BUG_74256300_FIXED || !mCompatMode) {
focusToPassword();
assertDatasetShown(mActivity.mPassword, "The Dude", "THE DUDE");
focusToUsername();
assertDatasetShown(mActivity.mUsername, "The Dude", "THE DUDE");
}
// Auto-fill it.
mUiBot.selectDataset("THE DUDE");
// Check the results.
mActivity.assertAutoFilled();
}
@Test
public void testAutofillOverrideDispatchProvideAutofillStructure() throws Exception {
mActivity.mCustomView.setOverrideDispatchProvideAutofillStructure(true);
autofillTest(true);
}
@Test
public void testAutofillManuallyOneDataset() throws Exception {
skipTestOnCompatMode(); // TODO(b/73557072): not supported yet
// Set service.
enableService();
// Set expectations.
sReplier.addResponse(new CannedDataset.Builder()
.setField(ID_USERNAME, "dude")
.setField(ID_PASSWORD, "sweet")
.setPresentation(createPresentation("The Dude"))
.build());
mActivity.expectAutoFill("dude", "sweet");
// Trigger auto-fill.
mActivity.requestAutofill(mActivity.mUsername);
sReplier.getNextFillRequest();
// Select datatest.
mUiBot.selectDataset("The Dude");
// Check the results.
mActivity.assertAutoFilled();
}
@Test
@AppModeFull(reason = "testAutofillManuallyOneDataset() is enough")
public void testAutofillManuallyTwoDatasetsPickFirst() throws Exception {
autofillManuallyTwoDatasets(true);
}
@Test
@AppModeFull(reason = "testAutofillManuallyOneDataset() is enough")
public void testAutofillManuallyTwoDatasetsPickSecond() throws Exception {
autofillManuallyTwoDatasets(false);
}
private void autofillManuallyTwoDatasets(boolean pickFirst) throws Exception {
skipTestOnCompatMode(); // TODO(b/73557072): not supported yet
// Set service.
enableService();
// Set expectations.
sReplier.addResponse(new CannedFillResponse.Builder()
.addDataset(new CannedDataset.Builder()
.setField(ID_USERNAME, "dude")
.setField(ID_PASSWORD, "sweet")
.setPresentation(createPresentation("The Dude"))
.build())
.addDataset(new CannedDataset.Builder()
.setField(ID_USERNAME, "jenny")
.setField(ID_PASSWORD, "8675309")
.setPresentation(createPresentation("Jenny"))
.build())
.build());
if (pickFirst) {
mActivity.expectAutoFill("dude", "sweet");
} else {
mActivity.expectAutoFill("jenny", "8675309");
}
// Trigger auto-fill.
mActivity.getSystemService(AutofillManager.class).requestAutofill(
mActivity.mCustomView, mActivity.mUsername.text.id,
mActivity.mUsername.getAbsCoordinates());
sReplier.getNextFillRequest();
// Auto-fill it.
final UiObject2 picker = assertDatasetShown(mActivity.mUsername, "The Dude", "Jenny");
mUiBot.selectDataset(picker, pickFirst ? "The Dude" : "Jenny");
// Check the results.
mActivity.assertAutoFilled();
}
@Test
public void testAutofillCallbacks() throws Exception {
// Set service.
enableService();
final MyAutofillCallback callback = mActivity.registerCallback();
// Set expectations.
sReplier.addResponse(new CannedDataset.Builder()
.setField(ID_USERNAME, "dude")
.setField(ID_PASSWORD, "sweet")
.setPresentation(createPresentation("The Dude"))
.build());
mActivity.expectAutoFill("dude", "sweet");
// Trigger auto-fill.
focusToUsername();
sReplier.getNextFillRequest();
callback.assertUiShownEvent(mActivity.mCustomView, mActivity.mUsername.text.id);
// Change focus
focusToPassword();
callback.assertUiHiddenEvent(mActivity.mCustomView, mActivity.mUsername.text.id);
callback.assertUiShownEvent(mActivity.mCustomView, mActivity.mPassword.text.id);
}
@Test
@AppModeFull(reason = "testAutofillCallbacks() is enough")
public void testAutofillCallbackDisabled() throws Throwable {
// Set service.
disableService();
final MyAutofillCallback callback = mActivity.registerCallback();
// Trigger auto-fill.
focusToUsernameExpectNoWindowEvent();
// Assert callback was called
callback.assertUiUnavailableEvent(mActivity.mCustomView, mActivity.mUsername.text.id);
}
@Test
@AppModeFull(reason = "testAutofillCallbacks() is enough")
public void testAutofillCallbackNoDatasets() throws Throwable {
// Set service.
enableService();
final MyAutofillCallback callback = mActivity.registerCallback();
// Set expectations.
sReplier.addResponse(NO_RESPONSE);
// Trigger autofill.
focusToUsernameExpectNoWindowEvent();
sReplier.getNextFillRequest();
// Auto-fill it.
mUiBot.assertNoDatasetsEver();
// Assert callback was called
callback.assertUiUnavailableEvent(mActivity.mCustomView, mActivity.mUsername.text.id);
}
@Test
@AppModeFull(reason = "testAutofillCallbacks() is enough")
public void testAutofillCallbackNoDatasetsButSaveInfo() throws Throwable {
// Set service.
enableService();
final MyAutofillCallback callback = mActivity.registerCallback();
// Set expectations.
sReplier.addResponse(new CannedFillResponse.Builder()
.setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
.build());
// Trigger autofill.
focusToUsernameExpectNoWindowEvent();
sReplier.getNextFillRequest();
// Autofill it.
mUiBot.assertNoDatasetsEver();
// Assert callback was called
callback.assertUiUnavailableEvent(mActivity.mCustomView, mActivity.mUsername.text.id);
// Make sure save is not triggered
mActivity.getAutofillManager().commit();
mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
}
@Test
public void testSaveDialogNotShownWhenBackIsPressed() throws Exception {
// Set service.
enableService();
// Set expectations.
sReplier.addResponse(new CannedFillResponse.Builder()
.addDataset(new CannedDataset.Builder()
.setField(ID_USERNAME, "dude")
.setField(ID_PASSWORD, "sweet")
.setPresentation(createPresentation("The Dude"))
.build())
.setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
.build());
mActivity.expectAutoFill("dude", "sweet");
// Trigger auto-fill.
focusToUsername();
sReplier.getNextFillRequest();
assertDatasetShown(mActivity.mUsername, "The Dude");
mUiBot.pressBack();
mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
}
@Test
public void testSave_childViewsGone_notifyAfm() throws Throwable {
saveTest(CommitType.CHILDREN_VIEWS_GONE_NOTIFY_CALLBACK_API);
}
@Test
public void testSave_childViewsGone_updateView() throws Throwable {
saveTest(CommitType.CHILDREN_VIEWS_GONE_NOTIFY_CALLBACK_API);
}
@Test
@Ignore("Disabled until b/73493342 is fixed")
public void testSave_parentViewGone() throws Throwable {
saveTest(CommitType.PARENT_VIEW_GONE);
}
@Test
public void testSave_appCallsCommit() throws Throwable {
saveTest(CommitType.EXPLICIT_COMMIT);
}
@Test
public void testSave_submitButtonClicked() throws Throwable {
saveTest(CommitType.SUBMIT_BUTTON_CLICKED);
}
enum CommitType {
CHILDREN_VIEWS_GONE_NOTIFY_CALLBACK_API,
CHILDREN_VIEWS_GONE_IS_VISIBLE_API,
PARENT_VIEW_GONE,
EXPLICIT_COMMIT,
SUBMIT_BUTTON_CLICKED
}
private void saveTest(CommitType commitType) throws Throwable {
// Set service.
enableService();
// Set expectations.
final CannedFillResponse.Builder response = new CannedFillResponse.Builder()
.setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD);
switch (commitType) {
case CHILDREN_VIEWS_GONE_NOTIFY_CALLBACK_API:
case CHILDREN_VIEWS_GONE_IS_VISIBLE_API:
case PARENT_VIEW_GONE:
response.setSaveInfoFlags(SaveInfo.FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE);
break;
case EXPLICIT_COMMIT:
// does nothing
break;
case SUBMIT_BUTTON_CLICKED:
response
.setSaveInfoFlags(SaveInfo.FLAG_DONT_SAVE_ON_FINISH)
.setSaveTriggerId(mActivity.mCustomView.mLoginButtonId);
break;
default:
throw new IllegalArgumentException("invalid type: " + commitType);
}
sReplier.addResponse(response.build());
// Trigger auto-fill.
focusToUsernameExpectNoWindowEvent();
sReplier.getNextFillRequest();
// Fill in some stuff
mActivity.mUsername.setText("foo");
focusToPasswordExpectNoWindowEvent();
mActivity.mPassword.setText("bar");
// Trigger save.
switch (commitType) {
case CHILDREN_VIEWS_GONE_NOTIFY_CALLBACK_API:
setViewsInvisible(VisibilityIntegrationMode.NOTIFY_AFM);
break;
case CHILDREN_VIEWS_GONE_IS_VISIBLE_API:
setViewsInvisible(VisibilityIntegrationMode.OVERRIDE_IS_VISIBLE_TO_USER);
break;
case PARENT_VIEW_GONE:
mActivity.runOnUiThread(() -> {
final ViewGroup parent = (ViewGroup) mActivity.mCustomView.getParent();
parent.removeView(mActivity.mCustomView);
});
break;
case EXPLICIT_COMMIT:
mActivity.getAutofillManager().commit();
break;
case SUBMIT_BUTTON_CLICKED:
mActivity.mCustomView.clickLogin();
break;
default:
throw new IllegalArgumentException("unknown type: " + commitType);
}
// Assert UI is showing.
mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
// Assert results
final SaveRequest saveRequest = sReplier.getNextSaveRequest();
final ViewNode username = findNodeByResourceId(saveRequest.structure, ID_USERNAME);
final ViewNode password = findNodeByResourceId(saveRequest.structure, ID_PASSWORD);
assertTextAndValue(username, "foo");
assertTextAndValue(password, "bar");
}
protected void setViewsInvisible(VisibilityIntegrationMode mode) {
mActivity.mUsername.setVisibilityIntegrationMode(mode);
mActivity.mPassword.setVisibilityIntegrationMode(mode);
mActivity.mUsername.changeVisibility(false);
mActivity.mPassword.changeVisibility(false);
}
// NOTE: tests where save is not shown only makes sense when calling commit() explicitly,
// otherwise the test could pass but the UI is still shown *after* the app is committed.
// We could still test them by explicitly committing and then checking that the Save UI is not
// shown again, but then we wouldn't be effectively testing that the context was committed
@Test
public void testSaveNotShown_noUserInput() throws Throwable {
// Set service.
enableService();
// Set expectations.
sReplier.addResponse(new CannedFillResponse.Builder()
.setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD).build());
// Trigger auto-fill.
focusToUsernameExpectNoWindowEvent();
sReplier.getNextFillRequest();
// Trigger save.
mActivity.getAutofillManager().commit();
// Assert it's not showing.
mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
}
@Test
@AppModeFull(reason = "testSaveNotShown_noUserInput() is enough")
public void testSaveNotShown_initialValues_noUserInput() throws Throwable {
// Prepare activitiy.
mActivity.mUsername.setText("foo");
mActivity.mPassword.setText("bar");
// Set service.
enableService();
// Set expectations.
sReplier.addResponse(new CannedFillResponse.Builder()
.setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD).build());
// Trigger auto-fill.
focusToUsernameExpectNoWindowEvent();
sReplier.getNextFillRequest();
// Trigger save.
mActivity.getAutofillManager().commit();
// Assert it's not showing.
mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
}
@Test
@AppModeFull(reason = "testSaveNotShown_noUserInput() is enough")
public void testSaveNotShown_initialValues_noUserInput_serviceDatasets() throws Throwable {
// Prepare activitiy.
mActivity.mUsername.setText("foo");
mActivity.mPassword.setText("bar");
// Set service.
enableService();
// Set expectations.
sReplier.addResponse(new CannedFillResponse.Builder()
.addDataset(new CannedDataset.Builder()
.setField(ID_USERNAME, "dude")
.setField(ID_PASSWORD, "sweet")
.setPresentation(createPresentation("The Dude"))
.build())
.setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD).build());
// Trigger auto-fill.
focusToUsernameExpectNoWindowEvent();
sReplier.getNextFillRequest();
assertDatasetShown(mActivity.mUsername, "The Dude");
// Trigger save.
mActivity.getAutofillManager().commit();
// Assert it's not showing.
mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
}
@Test
@AppModeFull(reason = "testSaveNotShown_noUserInput() is enough")
public void testSaveNotShown_userInputMatchesDatasets() throws Throwable {
// Prepare activitiy.
mActivity.mUsername.setText("foo");
mActivity.mPassword.setText("bar");
// Set service.
enableService();
// Set expectations.
sReplier.addResponse(new CannedFillResponse.Builder()
.addDataset(new CannedDataset.Builder()
.setField(ID_USERNAME, "foo")
.setField(ID_PASSWORD, "bar")
.setPresentation(createPresentation("The Dude"))
.build())
.setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD).build());
// Trigger auto-fill.
focusToUsernameExpectNoWindowEvent();
sReplier.getNextFillRequest();
assertDatasetShown(mActivity.mUsername, "The Dude");
// Trigger save.
mActivity.getAutofillManager().commit();
// Assert it's not showing.
mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
}
@Test
public void testDatasetFiltering() throws Throwable {
final String aa = "Two A's";
final String ab = "A and B";
final String b = "Only B";
enableService();
// Set expectations.
sReplier.addResponse(new CannedFillResponse.Builder()
.addDataset(new CannedDataset.Builder()
.setField(ID_USERNAME, "aa")
.setPresentation(createPresentation(aa))
.build())
.addDataset(new CannedDataset.Builder()
.setField(ID_USERNAME, "ab")
.setPresentation(createPresentation(ab))
.build())
.addDataset(new CannedDataset.Builder()
.setField(ID_USERNAME, "b")
.setPresentation(createPresentation(b))
.build())
.build());
// Trigger auto-fill.
focusToUsernameExpectNoWindowEvent();
sReplier.getNextFillRequest();
// With no filter text all datasets should be shown
assertDatasetShown(mActivity.mUsername, aa, ab, b);
// Only two datasets start with 'a'
mActivity.mUsername.setText("a");
assertDatasetShown(mActivity.mUsername, aa, ab);
// Only one dataset start with 'aa'
mActivity.mUsername.setText("aa");
assertDatasetShown(mActivity.mUsername, aa);
// Only two datasets start with 'a'
mActivity.mUsername.setText("a");
assertDatasetShown(mActivity.mUsername, aa, ab);
// With no filter text all datasets should be shown
mActivity.mUsername.setText("");
assertDatasetShown(mActivity.mUsername, aa, ab, b);
// No dataset start with 'aaa'
final MyAutofillCallback callback = mActivity.registerCallback();
mActivity.mUsername.setText("aaa");
callback.assertUiHiddenEvent(mActivity.mCustomView, mActivity.mUsername.text.id);
mUiBot.assertNoDatasets();
}
/**
* Asserts the dataset picker is properly displayed in a give line.
*/
protected UiObject2 assertDatasetShown(Line line, String... expectedDatasets)
throws Exception {
boolean autofillViewBoundsMatches = !Helper.isAutofillWindowFullScreen(mContext);
final UiObject2 datasetPicker = mUiBot.assertDatasets(expectedDatasets);
final Rect pickerBounds = datasetPicker.getVisibleBounds();
final Rect fieldBounds = line.getAbsCoordinates();
if (autofillViewBoundsMatches) {
assertWithMessage("vertical coordinates don't match; picker=%s, field=%s", pickerBounds,
fieldBounds).that(pickerBounds.top).isEqualTo(fieldBounds.bottom);
assertWithMessage("horizontal coordinates don't match; picker=%s, field=%s",
pickerBounds, fieldBounds).that(pickerBounds.left).isEqualTo(fieldBounds.left);
}
return datasetPicker;
}
protected void assertLabel(ViewNode node, String expectedValue) {
if (mCompatMode) {
// Compat mode doesn't set AutofillValue of non-editable fields
assertTextOnly(node, expectedValue);
} else {
assertTextAndValue(node, expectedValue);
}
}
protected void assertUrlBarIsSanitized(ViewNode urlBar) {
assertTextIsSanitized(urlBar);
assertThat(urlBar.getWebDomain()).isNull();
assertThat(urlBar.getWebScheme()).isNull();
}
private void skipTestOnCompatMode() {
assumeTrue("test not applicable when on compat mode", !mCompatMode);
}
}