blob: 698aa449953b02165c19251e649fddd2c44e599e [file] [log] [blame]
/*
* Copyright (C) 2018 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.Helper.ID_PASSWORD;
import static android.autofillservice.cts.Helper.ID_USERNAME;
import static android.autofillservice.cts.Helper.assertTextAndValue;
import static android.autofillservice.cts.Helper.assertTextIsSanitized;
import static android.autofillservice.cts.Helper.findNodeByResourceId;
import static android.autofillservice.cts.Helper.getContext;
import static android.autofillservice.cts.Helper.hasAutofillFeature;
import static android.autofillservice.cts.InstrumentedAutoFillServiceCompatMode.SERVICE_NAME;
import static android.autofillservice.cts.InstrumentedAutoFillServiceCompatMode.SERVICE_PACKAGE;
import static android.autofillservice.cts.VirtualContainerActivity.INITIAL_URL_BAR_VALUE;
import static android.autofillservice.cts.VirtualContainerView.ID_URL_BAR;
import static android.autofillservice.cts.VirtualContainerView.ID_URL_BAR2;
import static android.autofillservice.cts.common.SettingsHelper.NAMESPACE_GLOBAL;
import static android.provider.Settings.Global.AUTOFILL_COMPAT_MODE_ALLOWED_PACKAGES;
import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD;
import static com.google.common.truth.Truth.assertThat;
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.common.SettingsHelper;
import android.autofillservice.cts.common.SettingsStateChangerRule;
import android.content.Context;
import android.os.SystemClock;
import android.platform.test.annotations.AppModeFull;
import android.service.autofill.SaveInfo;
import androidx.test.InstrumentationRegistry;
import org.junit.After;
import org.junit.ClassRule;
import org.junit.Test;
/**
* Test case for an activity containing virtual children but using the A11Y compat mode to implement
* the Autofill APIs.
*/
public class VirtualContainerActivityCompatModeTest extends VirtualContainerActivityTest {
private static final Context sContext = InstrumentationRegistry.getContext();
@ClassRule
public static final SettingsStateChangerRule sCompatModeChanger = new SettingsStateChangerRule(
sContext, NAMESPACE_GLOBAL, AUTOFILL_COMPAT_MODE_ALLOWED_PACKAGES,
SERVICE_PACKAGE + "[my_url_bar]");
public VirtualContainerActivityCompatModeTest() {
super(true);
}
@After
public void resetCompatMode() {
sContext.getApplicationContext().setAutofillCompatibilityEnabled(false);
}
@Override
protected void preActivityCreated() {
sContext.getApplicationContext().setAutofillCompatibilityEnabled(true);
}
@Override
protected void postActivityLaunched(VirtualContainerActivity activity) {
// Set our own compat mode as well..
activity.mCustomView.setCompatMode(true);
}
@Override
protected void enableService() {
Helper.enableAutofillService(getContext(), SERVICE_NAME);
InstrumentedAutoFillServiceCompatMode.setIgnoreUnexpectedRequests(false);
}
@Override
protected void disableService() {
if (!hasAutofillFeature()) return;
Helper.disableAutofillService(getContext(), SERVICE_NAME);
InstrumentedAutoFillServiceCompatMode.setIgnoreUnexpectedRequests(true);
}
@Override
protected void assertUrlBarIsSanitized(ViewNode urlBar) {
assertTextIsSanitized(urlBar);
assertThat(urlBar.getWebDomain()).isEqualTo("dev.null");
assertThat(urlBar.getWebScheme()).isEqualTo("ftp");
}
@Test
public void testMultipleUrlBars_firstDoesNotExist() throws Exception {
SettingsHelper.syncSet(sContext, NAMESPACE_GLOBAL, AUTOFILL_COMPAT_MODE_ALLOWED_PACKAGES,
SERVICE_PACKAGE + "[first_am_i,my_url_bar]");
// Set service.
enableService();
// Set expectations.
sReplier.addResponse(new CannedDataset.Builder()
.setField(ID_USERNAME, "dude", createPresentation("DUDE"))
.build());
// Trigger autofill.
focusToUsername();
assertDatasetShown(mActivity.mUsername, "DUDE");
// Make sure input was sanitized.
final FillRequest request = sReplier.getNextFillRequest();
final ViewNode urlBar = findNodeByResourceId(request.structure, ID_URL_BAR);
assertUrlBarIsSanitized(urlBar);
}
@Test
@AppModeFull // testMultipleUrlBars_firstDoesNotExist() is enough to test ephemeral apps support
public void testMultipleUrlBars_bothExist() throws Exception {
SettingsHelper.syncSet(sContext, NAMESPACE_GLOBAL, AUTOFILL_COMPAT_MODE_ALLOWED_PACKAGES,
SERVICE_PACKAGE + "[my_url_bar,my_url_bar2]");
// Set service.
enableService();
// Set expectations.
sReplier.addResponse(new CannedDataset.Builder()
.setField(ID_USERNAME, "dude", createPresentation("DUDE"))
.build());
// Trigger autofill.
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 urlBar2 = findNodeByResourceId(request.structure, ID_URL_BAR2);
assertUrlBarIsSanitized(urlBar);
assertTextIsSanitized(urlBar2);
}
@Test
@AppModeFull // testMultipleUrlBars_firstDoesNotExist() is enough to test ephemeral apps support
public void testFocusOnUrlBarIsIgnored() 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();
mActivity.syncRunOnUiThread(() -> mActivity.mUrlBar.requestFocus());
// Must force sleep, as there is no callback that we can wait upon.
SystemClock.sleep(Timeouts.FILL_TIMEOUT.ms());
sReplier.assertNoUnhandledFillRequests();
}
@Test
@AppModeFull // testMultipleUrlBars_firstDoesNotExist() is enough to test ephemeral apps support
public void testUrlBarChangeIgnoredWhenServiceCanSave() throws Throwable {
// Set service.
enableService();
// Set expectations.
sReplier.addResponse(new CannedFillResponse.Builder()
.setSaveInfoFlags(SaveInfo.FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE)
.setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD).build());
// Trigger auto-fill.
focusToUsernameExpectNoWindowEvent();
sReplier.getNextFillRequest();
// Fill in some stuff
mActivity.mUsername.setText("foo");
focusToPasswordExpectNoWindowEvent();
mActivity.mPassword.setText("bar");
// Change URL bar before views become invisible
final OneTimeTextWatcher urlWatcher = new OneTimeTextWatcher("urlWatcher",
mActivity.mUrlBar, "http://null/dev");
mActivity.mUrlBar.addTextChangedListener(urlWatcher);
mActivity.syncRunOnUiThread(() -> mActivity.mUrlBar.setText("http://null/dev"));
urlWatcher.assertAutoFilled();
// Trigger save.
// TODO(b/76220569): ideally, save should be triggered by calling:
//
// setViewsInvisible(VisibilityIntegrationMode.OVERRIDE_IS_VISIBLE_TO_USER);
//
// But unfortunately that's not always working due to flakiness on showing the UI, hence
// we're forcing commit - after all, the point here is the the URL update above didn't
// cancel the session (which is the case on
// testUrlBarChangeCancelSessionWhenServiceCannotSave()
mActivity.getAutofillManager().commit();
// 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);
final ViewNode urlBar = findNodeByResourceId(saveRequest.structure, ID_URL_BAR);
assertTextAndValue(username, "foo");
assertTextAndValue(password, "bar");
// Make sure it's the URL bar from initial session.
assertTextAndValue(urlBar, INITIAL_URL_BAR_VALUE);
}
@Test
@AppModeFull // testMultipleUrlBars_firstDoesNotExist() is enough to test ephemeral apps support
public void testUrlBarChangeCancelSessionWhenServiceCannotSave() throws Throwable {
// 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())
// there's no SaveInfo here
.build());
// Trigger auto-fill.
focusToUsernameExpectNoWindowEvent();
sReplier.getNextFillRequest();
assertDatasetShown(mActivity.mUsername, "The Dude");
// Fill in some stuff
mActivity.mUsername.setText("foo");
focusToPasswordExpectNoWindowEvent();
mActivity.mPassword.setText("bar");
// Change URL bar before views become invisible
final OneTimeTextWatcher urlWatcher = new OneTimeTextWatcher("urlWatcher",
mActivity.mUrlBar, "http://null/dev");
mActivity.mUrlBar.addTextChangedListener(urlWatcher);
mActivity.syncRunOnUiThread(() -> mActivity.mUrlBar.setText("http://null/dev"));
urlWatcher.assertAutoFilled();
// Trigger save...
mActivity.getAutofillManager().commit();
// ... should not be triggered because the session was already canceled...
mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
}
@Test
@AppModeFull // testMultipleUrlBars_firstDoesNotExist() is enough to test ephemeral apps support
public void testUrlBarChangeCancelSessionWhenServiceReturnsNullResponse() throws Throwable {
// Set service.
enableService();
// Set expectations.
sReplier.addResponse(CannedFillResponse.NO_RESPONSE);
// Trigger auto-fill.
focusToUsernameExpectNoWindowEvent();
sReplier.getNextFillRequest();
// Fill in some stuff
mActivity.mUsername.setText("foo");
focusToPasswordExpectNoWindowEvent();
mActivity.mPassword.setText("bar");
// Change URL bar before views become invisible
final OneTimeTextWatcher urlWatcher = new OneTimeTextWatcher("urlWatcher",
mActivity.mUrlBar, "http://null/dev");
mActivity.mUrlBar.addTextChangedListener(urlWatcher);
mActivity.syncRunOnUiThread(() -> mActivity.mUrlBar.setText("http://null/dev"));
urlWatcher.assertAutoFilled();
// Trigger save...
mActivity.getAutofillManager().commit();
// ... should not be triggered because the session was already canceled...
mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
}
}