| /* |
| * 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); |
| } |
| } |