blob: b6d94a2f667942f9115da95d95bec1e0c4bdc927 [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.AbstractWebViewActivity.HTML_NAME_PASSWORD;
import static android.autofillservice.cts.AbstractWebViewActivity.HTML_NAME_USERNAME;
import static android.autofillservice.cts.CustomDescriptionHelper.newCustomDescriptionWithUsernameAndPassword;
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.findNodeByHtmlName;
import static android.autofillservice.cts.Helper.getAutofillId;
import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD;
import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_USERNAME;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import android.app.assist.AssistStructure;
import android.autofillservice.cts.InstrumentedAutoFillService.FillRequest;
import android.autofillservice.cts.InstrumentedAutoFillService.SaveRequest;
import android.content.ComponentName;
import android.service.autofill.CharSequenceTransformation;
import android.service.autofill.SaveInfo;
import android.support.test.uiautomator.UiObject2;
import android.util.Log;
import android.view.KeyEvent;
import android.view.autofill.AutofillId;
import org.junit.Test;
import java.util.concurrent.atomic.AtomicReference;
import java.util.regex.Pattern;
public class WebViewMultiScreenLoginActivityTest
extends AbstractWebViewTestCase<WebViewMultiScreenLoginActivity> {
private static final String TAG = "WebViewMultiScreenLoginTest";
private static final Pattern MATCH_ALL = Pattern.compile("^(.*)$");
private WebViewMultiScreenLoginActivity mActivity;
@Override
protected AutofillActivityTestRule<WebViewMultiScreenLoginActivity> getActivityRule() {
return new AutofillActivityTestRule<WebViewMultiScreenLoginActivity>(
WebViewMultiScreenLoginActivity.class) {
// TODO(b/111838239): latest WebView implementation calls AutofillManager.isEnabled() to
// disable autofill for optimization when it returns false, and unfortunately the value
// returned by that method does not change when the service is enabled / disabled, so we
// need to start enable the service before launching the activity.
// Once that's fixed, remove this overridden method.
@Override
protected void beforeActivityLaunched() {
super.beforeActivityLaunched();
Log.i(TAG, "Setting service before launching the activity");
enableService();
}
@Override
protected void afterActivityLaunched() {
mActivity = getActivity();
}
};
}
@Test
public void testSave_eachFieldSeparately() throws Exception {
// Set service.
enableService();
// Load WebView
final MyWebView myWebView = mActivity.loadWebView(mUiBot);
// Sanity check to make sure autofill is enabled in the application context
Helper.assertAutofillEnabled(myWebView.getContext(), true);
/*
* First screen: username
*/
sReplier.addResponse(new CannedFillResponse.Builder()
.setRequiredSavableIds(SAVE_DATA_TYPE_USERNAME, HTML_NAME_USERNAME)
.setSaveInfoDecorator((builder, nodeResolver) -> {
final AutofillId usernameId = getAutofillId(nodeResolver, HTML_NAME_USERNAME);
final CharSequenceTransformation usernameTrans = new CharSequenceTransformation
.Builder(usernameId, MATCH_ALL, "$1").build();
builder.setCustomDescription(newCustomDescriptionWithUsernameAndPassword()
.addChild(R.id.username, usernameTrans)
.build());
})
.build());
// Trigger autofill.
mActivity.getUsernameInput().click();
final FillRequest fillRequest1 = sReplier.getNextFillRequest();
assertThat(fillRequest1.contexts).hasSize(1);
mUiBot.assertNoDatasetsEver();
// Now trigger save.
if (INJECT_EVENTS) {
mActivity.getUsernameInput().click();
mActivity.dispatchKeyPress(KeyEvent.KEYCODE_D);
mActivity.dispatchKeyPress(KeyEvent.KEYCODE_U);
mActivity.dispatchKeyPress(KeyEvent.KEYCODE_D);
mActivity.dispatchKeyPress(KeyEvent.KEYCODE_E);
} else {
mActivity.getUsernameInput().setText("dude");
}
mActivity.getNextButton().click();
// Assert UI
final UiObject2 saveUi1 = mUiBot.assertSaveShowing(
SaveInfo.NEGATIVE_BUTTON_STYLE_CANCEL, null, SAVE_DATA_TYPE_USERNAME);
mUiBot.assertChildText(saveUi1, ID_USERNAME_LABEL, "User:");
mUiBot.assertChildText(saveUi1, ID_USERNAME, "dude");
// Assert save request
mUiBot.saveForAutofill(saveUi1, true);
final SaveRequest saveRequest1 = sReplier.getNextSaveRequest();
assertThat(saveRequest1.contexts).hasSize(1);
assertTextAndValue(findNodeByHtmlName(saveRequest1.structure, HTML_NAME_USERNAME), "dude");
/*
* Second screen: password
*/
mActivity.waitForPasswordScreen(mUiBot);
sReplier.addResponse(new CannedFillResponse.Builder()
.setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, HTML_NAME_PASSWORD)
.setSaveInfoDecorator((builder, nodeResolver) -> {
final AutofillId passwordId = getAutofillId(nodeResolver, HTML_NAME_PASSWORD);
final CharSequenceTransformation passwordTrans = new CharSequenceTransformation
.Builder(passwordId, MATCH_ALL, "$1").build();
builder.setCustomDescription(newCustomDescriptionWithUsernameAndPassword()
.addChild(R.id.password, passwordTrans)
.build());
})
.build());
// Trigger autofill.
mActivity.getPasswordInput().click();
final FillRequest fillRequest2 = sReplier.getNextFillRequest();
assertThat(fillRequest2.contexts).hasSize(1);
mUiBot.assertNoDatasetsEver();
// Now trigger save.
if (INJECT_EVENTS) {
mActivity.getPasswordInput().click();
mActivity.dispatchKeyPress(KeyEvent.KEYCODE_S);
mActivity.dispatchKeyPress(KeyEvent.KEYCODE_W);
mActivity.dispatchKeyPress(KeyEvent.KEYCODE_E);
mActivity.dispatchKeyPress(KeyEvent.KEYCODE_E);
mActivity.dispatchKeyPress(KeyEvent.KEYCODE_T);
} else {
mActivity.getPasswordInput().setText("sweet");
}
mActivity.getLoginButton().click();
// Assert save UI shown.
final UiObject2 saveUi2 = mUiBot.assertSaveShowing(
SaveInfo.NEGATIVE_BUTTON_STYLE_CANCEL, null, SAVE_DATA_TYPE_PASSWORD);
mUiBot.assertChildText(saveUi2, ID_PASSWORD_LABEL, "Pass:");
mUiBot.assertChildText(saveUi2, ID_PASSWORD, "sweet");
// Assert save request
mUiBot.saveForAutofill(saveUi2, true);
final SaveRequest saveRequest2 = sReplier.getNextSaveRequest();
assertThat(saveRequest2.contexts).hasSize(1);
assertTextAndValue(findNodeByHtmlName(saveRequest2.structure, HTML_NAME_PASSWORD), "sweet");
}
@Test
public void testSave_bothFieldsAtOnce() throws Exception {
// Set service.
enableService();
// Load WebView
final MyWebView myWebView = mActivity.loadWebView(mUiBot);
// Sanity check to make sure autofill is enabled in the application context
Helper.assertAutofillEnabled(myWebView.getContext(), true);
/*
* First screen: username
*/
final AtomicReference<AutofillId> usernameId = new AtomicReference<>();
sReplier.addResponse(new CannedFillResponse.Builder()
.setIgnoreFields(HTML_NAME_USERNAME)
.setSaveInfoFlags(SaveInfo.FLAG_DELAY_SAVE)
.setSaveInfoDecorator((builder, nodeResolver) -> {
usernameId.set(getAutofillId(nodeResolver, HTML_NAME_USERNAME));
})
.build());
// Trigger autofill.
mActivity.getUsernameInput().click();
final FillRequest fillRequest1 = sReplier.getNextFillRequest();
assertThat(fillRequest1.contexts).hasSize(1);
mUiBot.assertNoDatasetsEver();
// Change username
if (INJECT_EVENTS) {
mActivity.getUsernameInput().click();
mActivity.dispatchKeyPress(KeyEvent.KEYCODE_D);
mActivity.dispatchKeyPress(KeyEvent.KEYCODE_U);
mActivity.dispatchKeyPress(KeyEvent.KEYCODE_D);
mActivity.dispatchKeyPress(KeyEvent.KEYCODE_E);
} else {
mActivity.getUsernameInput().setText("dude");
}
mActivity.getNextButton().click();
// Assert UI
mUiBot.assertSaveNotShowing();
/*
* Second screen: password
*/
mActivity.waitForPasswordScreen(mUiBot);
sReplier.addResponse(new CannedFillResponse.Builder()
.setRequiredSavableIds(SAVE_DATA_TYPE_USERNAME | SAVE_DATA_TYPE_PASSWORD,
HTML_NAME_PASSWORD)
.setSaveInfoDecorator((builder, nodeResolver) -> {
final AutofillId passwordId = getAutofillId(nodeResolver, HTML_NAME_PASSWORD);
final CharSequenceTransformation usernameTrans = new CharSequenceTransformation
.Builder(usernameId.get(), MATCH_ALL, "$1").build();
final CharSequenceTransformation passwordTrans = new CharSequenceTransformation
.Builder(passwordId, MATCH_ALL, "$1").build();
Log.d(TAG, "setting CustomDescription: u=" + usernameId + ", p=" + passwordId);
builder.setCustomDescription(newCustomDescriptionWithUsernameAndPassword()
.addChild(R.id.username, usernameTrans)
.addChild(R.id.password, passwordTrans)
.build());
})
.build());
// Trigger autofill.
mActivity.getPasswordInput().click();
final FillRequest fillRequest2 = sReplier.getNextFillRequest();
assertThat(fillRequest2.contexts).hasSize(2);
mUiBot.assertNoDatasetsEver();
// Now trigger save.
if (INJECT_EVENTS) {
mActivity.getPasswordInput().click();
mActivity.dispatchKeyPress(KeyEvent.KEYCODE_S);
mActivity.dispatchKeyPress(KeyEvent.KEYCODE_W);
mActivity.dispatchKeyPress(KeyEvent.KEYCODE_E);
mActivity.dispatchKeyPress(KeyEvent.KEYCODE_E);
mActivity.dispatchKeyPress(KeyEvent.KEYCODE_T);
} else {
mActivity.getPasswordInput().setText("sweet");
}
mActivity.getLoginButton().click();
// Assert save UI shown.
final UiObject2 saveUi = mUiBot.assertSaveShowing(
SaveInfo.NEGATIVE_BUTTON_STYLE_CANCEL, null, SAVE_DATA_TYPE_USERNAME,
SAVE_DATA_TYPE_PASSWORD);
mUiBot.assertChildText(saveUi, ID_PASSWORD_LABEL, "Pass:");
mUiBot.assertChildText(saveUi, ID_PASSWORD, "sweet");
mUiBot.assertChildText(saveUi, ID_USERNAME_LABEL, "User:");
mUiBot.assertChildText(saveUi, ID_USERNAME, "dude");
// Assert save request
mUiBot.saveForAutofill(saveUi, true);
final SaveRequest saveRequest = sReplier.getNextSaveRequest();
// Username is set in the 1st context
final AssistStructure previousStructure = saveRequest.contexts.get(0).getStructure();
assertWithMessage("no structure for 1st activity").that(previousStructure).isNotNull();
assertTextAndValue(findNodeByHtmlName(previousStructure, HTML_NAME_USERNAME), "dude");
final ComponentName componentPrevious = previousStructure.getActivityComponent();
assertThat(componentPrevious).isEqualTo(mActivity.getComponentName());
// Password is set in the 2nd context
final AssistStructure currentStructure = saveRequest.contexts.get(1).getStructure();
assertWithMessage("no structure for 2nd activity").that(currentStructure).isNotNull();
assertTextAndValue(findNodeByHtmlName(currentStructure, HTML_NAME_PASSWORD), "sweet");
final ComponentName componentCurrent = currentStructure.getActivityComponent();
assertThat(componentCurrent).isEqualTo(mActivity.getComponentName());
}
}