blob: 545a91746052289811c37c949c87a6b1835ecbfd [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.activities.GridActivity.ID_L1C1;
import static android.autofillservice.cts.activities.GridActivity.ID_L1C2;
import static android.autofillservice.cts.testcore.Helper.assertEqualsIgnoreSession;
import static android.autofillservice.cts.testcore.Helper.assertTextIsSanitized;
import static android.autofillservice.cts.testcore.Helper.findNodeByResourceId;
import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_GENERIC;
import static com.google.common.truth.Truth.assertThat;
import static org.testng.Assert.assertThrows;
import android.app.assist.AssistStructure;
import android.app.assist.AssistStructure.ViewNode;
import android.autofillservice.cts.activities.GridActivity.FillExpectation;
import android.autofillservice.cts.commontests.AbstractGridActivityTestCase;
import android.autofillservice.cts.testcore.CannedFillResponse;
import android.autofillservice.cts.testcore.CannedFillResponse.CannedDataset;
import android.autofillservice.cts.testcore.InstrumentedAutoFillService.FillRequest;
import android.autofillservice.cts.testcore.InstrumentedAutoFillService.SaveRequest;
import android.service.autofill.FillContext;
import android.util.Log;
import android.view.autofill.AutofillId;
import android.widget.EditText;
import androidx.test.uiautomator.UiObject2;
import org.junit.Test;
import java.util.List;
/**
* Test cases for the cases where the autofill id of a view is changed by the app.
*/
public class MutableAutofillIdTest extends AbstractGridActivityTestCase {
private static final String TAG = "MutableAutofillIdTest";
@Test
public void testDatasetPickerIsNotShownAfterViewIsSwappedOut() throws Exception {
enableService();
final EditText field1 = mActivity.getCell(1, 1);
final AutofillId oldIdField1 = field1.getAutofillId();
// Prepare response
final CannedFillResponse response1 = new CannedFillResponse.Builder()
.addDataset(new CannedDataset.Builder()
.setField(ID_L1C1, "l1c1", createPresentation("l1c1"))
.setField(ID_L1C2, "l1c2", createPresentation("l1c2"))
.build())
.build();
sReplier.addResponse(response1);
// Trigger autofill on 1st view.
focusCell(1, 1);
final FillRequest fillRequest1 = sReplier.getNextFillRequest();
final ViewNode node1Request1 = assertTextIsSanitized(fillRequest1.structure, ID_L1C1);
assertEqualsIgnoreSession(node1Request1.getAutofillId(), oldIdField1);
mUiBot.assertDatasets("l1c1");
// Make sure 2nd field shows picker
focusCell(1, 2);
mUiBot.assertDatasets("l1c2");
// Now change id of 1st view
final AutofillId newIdField1 = mActivity.getAutofillManager().getNextAutofillId();
// TODO: move to an autofill unit test class for View
// Make sure view has to be detached first
assertThrows(IllegalStateException.class, () -> field1.setAutofillId(newIdField1));
// Change id
mActivity.removeCell(1, 1);
// TODO: move to an autofill unit test class for View
// Also assert it does not accept virtual ids
assertThrows(IllegalStateException.class,
() -> field1.setAutofillId(new AutofillId(newIdField1, 42)));
field1.setAutofillId(newIdField1);
assertThat(field1.getAutofillId()).isEqualTo(newIdField1);
Log.d(TAG, "Changed id of " + ID_L1C1 + " from " + oldIdField1 + " to " + newIdField1);
// Trigger another request because 1st view has a different id. Service will ignore it now.
final CannedFillResponse response2 = new CannedFillResponse.Builder()
.addDataset(new CannedDataset.Builder()
.setField(ID_L1C2, "l1c2", createPresentation("l1c2"))
.build())
.build();
sReplier.addResponse(response2);
// Re-add the cell before triggering autofill on it
mActivity.addCell(1, 1, field1);
mActivity.focusCell(1, 1);
final FillRequest fillRequest2 = sReplier.getNextFillRequest();
final ViewNode node1Request2 = assertTextIsSanitized(fillRequest2.structure, ID_L1C1);
// Make sure node has new id.
assertEqualsIgnoreSession(node1Request2.getAutofillId(), newIdField1);
mUiBot.assertNoDatasets();
// Make sure 2nd field shows picker
focusCell(1, 2);
final UiObject2 datasetPicker = mUiBot.assertDatasets("l1c2");
// Now autofill
final FillExpectation expectation = mActivity.expectAutofill()
.onCell(1, 2, "l1c2");
mUiBot.selectDataset(datasetPicker, "l1c2");
expectation.assertAutoFilled();
}
@Test
public void testViewGoneDuringAutofillCanStillBeFilled() throws Exception {
enableService();
final EditText field1 = mActivity.getCell(1, 1);
final AutofillId oldIdField1 = field1.getAutofillId();
// Prepare response
final CannedFillResponse response1 = new CannedFillResponse.Builder()
.addDataset(new CannedDataset.Builder()
.setField(ID_L1C1, "l1c1", createPresentation("l1c1"))
.setField(ID_L1C2, "l1c2", createPresentation("l1c2"))
.build())
.build();
sReplier.addResponse(response1);
// Trigger autofill on 1st view.
focusCell(1, 1);
final FillRequest fillRequest1 = sReplier.getNextFillRequest();
final ViewNode node1Request1 = assertTextIsSanitized(fillRequest1.structure, ID_L1C1);
assertEqualsIgnoreSession(node1Request1.getAutofillId(), oldIdField1);
mUiBot.assertDatasets("l1c1");
// Make sure 2nd field shows picker
focusCell(1, 2);
final UiObject2 picker1 = mUiBot.assertDatasets("l1c2");
// Now change id of 1st view
final AutofillId newIdField1 = mActivity.getAutofillManager().getNextAutofillId();
// Change id
mActivity.removeCell(1, 1);
field1.setAutofillId(newIdField1);
assertEqualsIgnoreSession(field1.getAutofillId(), newIdField1);
Log.d(TAG, "Changed id of " + ID_L1C1 + " from " + oldIdField1 + " to " + newIdField1);
// Autofill it - just 2nd field should be autofilled
final FillExpectation expectation1 = mActivity.expectAutofill().onCell(1, 2, "l1c2");
mUiBot.selectDataset(picker1, "l1c2");
expectation1.assertAutoFilled();
// Re-add the cell before triggering autofill on it
field1.setAutofillId(oldIdField1);
mActivity.addCell(1, 1, field1);
// Tap 1st field again - it should be autofillable
mActivity.focusCell(1, 1);
final UiObject2 picker2 = mUiBot.assertDatasets("l1c1");
// Now autofill again
final FillExpectation expectation2 = mActivity.expectAutofill().onCell(1, 1, "l1c1");
mUiBot.selectDataset(picker2, "l1c1");
expectation2.assertAutoFilled();
}
@Test
public void testSave_serviceIgnoresNewId() throws Exception {
saveWhenIdChanged(true);
}
@Test
public void testSave_serviceExpectingOldId() throws Exception {
saveWhenIdChanged(false);
}
private void saveWhenIdChanged(boolean serviceIgnoresNewId) throws Exception {
enableService();
final EditText field1 = mActivity.getCell(1, 1);
final AutofillId oldIdField1 = field1.getAutofillId();
// Prepare response
final CannedFillResponse response1 = new CannedFillResponse.Builder()
.setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_L1C1, ID_L1C2)
.build();
sReplier.addResponse(response1);
// Trigger autofill on 1st view.
mActivity.focusCell(1, 1); // No window change because it's not showing dataset picker.
final FillRequest fillRequest1 = sReplier.getNextFillRequest();
final ViewNode node1Request1 = assertTextIsSanitized(fillRequest1.structure, ID_L1C1);
assertEqualsIgnoreSession(node1Request1.getAutofillId(), oldIdField1);
mUiBot.assertNoDatasetsEver();
// Make sure 2nd field doesn't trigger a new request
mActivity.focusCell(1, 2); // No window change because it's not showing dataset picker.
mUiBot.assertNoDatasetsEver();
// Now change 1st view value...
mActivity.setText(1, 1, "OLD");
// ...and its id
final AutofillId newIdField1 = mActivity.getAutofillManager().getNextAutofillId();
mActivity.removeCell(1, 1);
field1.setAutofillId(newIdField1);
assertThat(field1.getAutofillId()).isEqualTo(newIdField1);
Log.d(TAG, "Changed id of " + ID_L1C1 + " from " + oldIdField1 + " to " + newIdField1);
// Trigger another request because 1st view has a different id...
final CannedFillResponse.Builder response2 = new CannedFillResponse.Builder();
if (serviceIgnoresNewId) {
// ... and service will ignore it now.
response2.setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_L1C2);
} else {
// ..but service is still expecting the old id.
response2.setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_L1C1, ID_L1C2);
}
sReplier.addResponse(response2.build());
mActivity.addCell(1, 1, field1);
mActivity.focusCell(1, 1); // No window change because it's not showing dataset picker.
final FillRequest fillRequest2 = sReplier.getNextFillRequest();
final ViewNode node1Request2 = assertTextIsSanitized(fillRequest2.structure, ID_L1C1);
// Make sure node has new id.
assertEqualsIgnoreSession(node1Request2.getAutofillId(), newIdField1);
mUiBot.assertNoDatasetsEver();
// Now triggers save
mActivity.setText(1, 1, "NEW");
mActivity.setText(1, 2, "NOD2");
mActivity.save();
mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC);
final SaveRequest saveRequest = sReplier.getNextSaveRequest();
final List<FillContext> contexts = saveRequest.contexts;
assertThat(contexts).hasSize(2);
// Assert 1st context
final AssistStructure structure1 = contexts.get(0).getStructure();
final ViewNode newNode1Context1 = findNodeByResourceId(structure1, ID_L1C1);
assertThat(newNode1Context1).isNotNull();
assertThat(newNode1Context1.getText().toString()).isEqualTo("OLD");
final ViewNode node2Context1 = findNodeByResourceId(structure1, ID_L1C2);
assertThat(node2Context1).isNotNull();
assertThat(node2Context1.getText().toString()).isEqualTo("NOD2");
// Assert 2nd context
final AssistStructure structure2 = contexts.get(1).getStructure();
final ViewNode newNode1Context2 = findNodeByResourceId(structure2, ID_L1C1);
assertThat(newNode1Context2).isNotNull();
assertThat(newNode1Context2.getText().toString()).isEqualTo("NEW");
final ViewNode node2Context2 = findNodeByResourceId(structure2, ID_L1C2);
assertThat(node2Context2).isNotNull();
assertThat(node2Context2.getText().toString()).isEqualTo("NOD2");
}
}