blob: ff25596bbf434e5520471273599020a7c67103df [file] [log] [blame]
/*
* Copyright 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.GridActivity.ID_L1C1;
import static android.autofillservice.cts.GridActivity.ID_L1C2;
import static android.autofillservice.cts.GridActivity.ID_L2C1;
import static android.autofillservice.cts.GridActivity.ID_L2C2;
import static android.autofillservice.cts.Helper.assertFillEventForContextCommitted;
import static android.autofillservice.cts.Helper.assertFillEventForFieldsClassification;
import static android.autofillservice.cts.Helper.findAutofillIdByResourceId;
import static android.provider.Settings.Secure.AUTOFILL_FEATURE_FIELD_CLASSIFICATION;
import static android.provider.Settings.Secure.AUTOFILL_USER_DATA_MAX_CATEGORY_COUNT;
import static android.provider.Settings.Secure.AUTOFILL_USER_DATA_MAX_FIELD_CLASSIFICATION_IDS_SIZE;
import static android.provider.Settings.Secure.AUTOFILL_USER_DATA_MAX_USER_DATA_SIZE;
import static android.provider.Settings.Secure.AUTOFILL_USER_DATA_MAX_VALUE_LENGTH;
import static android.provider.Settings.Secure.AUTOFILL_USER_DATA_MIN_VALUE_LENGTH;
import static android.service.autofill.AutofillFieldClassificationService.REQUIRED_ALGORITHM_EDIT_DISTANCE;
import static android.service.autofill.AutofillFieldClassificationService.REQUIRED_ALGORITHM_EXACT_MATCH;
import static com.google.common.truth.Truth.assertThat;
import android.autofillservice.cts.Helper.FieldClassificationResult;
import android.os.Bundle;
import android.platform.test.annotations.AppModeFull;
import android.service.autofill.FillContext;
import android.service.autofill.FillEventHistory.Event;
import android.service.autofill.UserData;
import android.view.autofill.AutofillId;
import android.view.autofill.AutofillManager;
import android.widget.EditText;
import com.android.compatibility.common.util.SettingsStateChangerRule;
import org.junit.ClassRule;
import org.junit.Test;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
@AppModeFull(reason = "Service-specific test")
public class FieldsClassificationTest extends AbstractGridActivityTestCase {
@ClassRule
public static final SettingsStateChangerRule sFeatureEnabler =
new SettingsStateChangerRule(sContext, AUTOFILL_FEATURE_FIELD_CLASSIFICATION, "1");
@ClassRule
public static final SettingsStateChangerRule sUserDataMaxFcSizeChanger =
new SettingsStateChangerRule(sContext,
AUTOFILL_USER_DATA_MAX_FIELD_CLASSIFICATION_IDS_SIZE, "10");
@ClassRule
public static final SettingsStateChangerRule sUserDataMaxUserSizeChanger =
new SettingsStateChangerRule(sContext, AUTOFILL_USER_DATA_MAX_USER_DATA_SIZE, "9");
@ClassRule
public static final SettingsStateChangerRule sUserDataMinValueChanger =
new SettingsStateChangerRule(sContext, AUTOFILL_USER_DATA_MIN_VALUE_LENGTH, "4");
@ClassRule
public static final SettingsStateChangerRule sUserDataMaxValueChanger =
new SettingsStateChangerRule(sContext, AUTOFILL_USER_DATA_MAX_VALUE_LENGTH, "50");
@ClassRule
public static final SettingsStateChangerRule sUserDataMaxCategoryChanger =
new SettingsStateChangerRule(sContext, AUTOFILL_USER_DATA_MAX_CATEGORY_COUNT, "42");
private AutofillManager mAfm;
private Bundle mLast4Bundle = new Bundle();
@Override
protected void postActivityLaunched() {
mAfm = mActivity.getAutofillManager();
mLast4Bundle.putInt("suffix", 4);
}
@Test
public void testFeatureIsEnabled() throws Exception {
enableService();
assertThat(mAfm.isFieldClassificationEnabled()).isTrue();
disableService();
assertThat(mAfm.isFieldClassificationEnabled()).isFalse();
}
@Test
public void testGetAlgorithm() throws Exception {
enableService();
// Check algorithms
final List<String> names = mAfm.getAvailableFieldClassificationAlgorithms();
assertThat(names.size()).isAtLeast(1);
final String defaultAlgorithm = mAfm.getDefaultFieldClassificationAlgorithm();
assertThat(defaultAlgorithm).isNotEmpty();
assertThat(names).contains(defaultAlgorithm);
// Checks invalid service
disableService();
assertThat(mAfm.getAvailableFieldClassificationAlgorithms()).isEmpty();
}
@Test
public void testUserData() throws Exception {
assertThat(mAfm.getUserData()).isNull();
assertThat(mAfm.getUserDataId()).isNull();
enableService();
mAfm.setUserData(new UserData.Builder("user_data_id", "value", "remote_id")
.build());
assertThat(mAfm.getUserData()).isNotNull();
assertThat(mAfm.getUserDataId()).isEqualTo("user_data_id");
final UserData userData = mAfm.getUserData();
assertThat(userData.getId()).isEqualTo("user_data_id");
assertThat(userData.getFieldClassificationAlgorithm()).isNull();
assertThat(userData.getFieldClassificationAlgorithms()).isNull();
disableService();
assertThat(mAfm.getUserData()).isNull();
assertThat(mAfm.getUserDataId()).isNull();
}
@Test
public void testRequiredAlgorithmsAvailable() throws Exception {
enableService();
final List<String> availableAlgorithms = mAfm.getAvailableFieldClassificationAlgorithms();
assertThat(availableAlgorithms).isNotNull();
assertThat(availableAlgorithms.contains(REQUIRED_ALGORITHM_EDIT_DISTANCE)).isTrue();
assertThat(availableAlgorithms.contains(REQUIRED_ALGORITHM_EXACT_MATCH)).isTrue();
}
@Test
public void testUserDataConstraints() throws Exception {
// NOTE: values set by the SettingsStateChangerRule @Rules should have unique values to
// make sure the getters below are reading the right property.
assertThat(UserData.getMaxFieldClassificationIdsSize()).isEqualTo(10);
assertThat(UserData.getMaxUserDataSize()).isEqualTo(9);
assertThat(UserData.getMinValueLength()).isEqualTo(4);
assertThat(UserData.getMaxValueLength()).isEqualTo(50);
assertThat(UserData.getMaxCategoryCount()).isEqualTo(42);
}
@Test
public void testHit_oneUserData_oneDetectableField() throws Exception {
simpleHitTest(false, null);
}
@Test
public void testHit_invalidAlgorithmIsIgnored() throws Exception {
// For simplicity's sake, let's assume that name will never be valid..
String invalidName = " ALGORITHM, Y NO INVALID? ";
simpleHitTest(true, invalidName);
}
@Test
public void testHit_userDataAlgorithmIsReset() throws Exception {
simpleHitTest(true, null);
}
@Test
public void testMiss_exactMatchAlgorithm() throws Exception {
enableService();
// Set expectations.
mAfm.setUserData(new UserData
.Builder("id", "t 1234", "cat")
.setFieldClassificationAlgorithmForCategory("cat",
REQUIRED_ALGORITHM_EXACT_MATCH, mLast4Bundle)
.build());
final MyAutofillCallback callback = mActivity.registerCallback();
final EditText field = mActivity.getCell(1, 1);
sReplier.addResponse(new CannedFillResponse.Builder()
.setFieldClassificationIds(ID_L1C1)
.build());
// Trigger autofill
mActivity.focusCell(1, 1);
sReplier.getNextFillRequest();
mUiBot.assertNoDatasetsEver();
callback.assertUiUnavailableEvent(field);
// Simulate user input
mActivity.setText(1, 1, "t 5678");
// Finish context.
mAfm.commit();
// Assert results
final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
assertFillEventForFieldsClassification(events.get(0), null);
}
@Test
public void testHit_exactMatchLast4Algorithm() throws Exception {
enableService();
// Set expectations.
mAfm.setUserData(new UserData
.Builder("id", "1234", "cat")
.setFieldClassificationAlgorithmForCategory("cat",
REQUIRED_ALGORITHM_EXACT_MATCH, mLast4Bundle)
.build());
final MyAutofillCallback callback = mActivity.registerCallback();
final EditText field = mActivity.getCell(1, 1);
final AtomicReference<AutofillId> fieldId = new AtomicReference<>();
sReplier.addResponse(new CannedFillResponse.Builder()
.setFieldClassificationIds(ID_L1C1)
.setVisitor((contexts, builder) -> fieldId
.set(findAutofillIdByResourceId(contexts.get(0), ID_L1C1)))
.build());
// Trigger autofill
mActivity.focusCell(1, 1);
sReplier.getNextFillRequest();
mUiBot.assertNoDatasetsEver();
callback.assertUiUnavailableEvent(field);
// Simulate user input
mActivity.setText(1, 1, "T1234");
// Finish context.
mAfm.commit();
// Assert results
final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
assertFillEventForFieldsClassification(events.get(0), fieldId.get(), "cat", 1);
}
@Test
public void testHit_useDefaultAlgorithm() throws Exception {
enableService();
// Set expectations.
mAfm.setUserData(new UserData
.Builder("id", "1234", "cat")
.setFieldClassificationAlgorithm(REQUIRED_ALGORITHM_EXACT_MATCH, mLast4Bundle)
.setFieldClassificationAlgorithmForCategory("dog",
REQUIRED_ALGORITHM_EDIT_DISTANCE, null)
.build());
final MyAutofillCallback callback = mActivity.registerCallback();
final EditText field = mActivity.getCell(1, 1);
final AtomicReference<AutofillId> fieldId = new AtomicReference<>();
sReplier.addResponse(new CannedFillResponse.Builder()
.setFieldClassificationIds(ID_L1C1)
.setVisitor((contexts, builder) -> fieldId
.set(findAutofillIdByResourceId(contexts.get(0), ID_L1C1)))
.build());
// Trigger autofill
mActivity.focusCell(1, 1);
sReplier.getNextFillRequest();
mUiBot.assertNoDatasetsEver();
callback.assertUiUnavailableEvent(field);
// Simulate user input
mActivity.setText(1, 1, "T1234");
// Finish context.
mAfm.commit();
// Assert results
final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
assertFillEventForFieldsClassification(events.get(0), fieldId.get(), "cat", 1);
}
private void simpleHitTest(boolean setAlgorithm, String algorithm) throws Exception {
// Set service.
enableService();
// Set expectations.
final UserData.Builder userData = new UserData.Builder("id", "FULLY", "myId");
if (setAlgorithm) {
userData.setFieldClassificationAlgorithm(algorithm, null);
}
mAfm.setUserData(userData.build());
final MyAutofillCallback callback = mActivity.registerCallback();
final EditText field = mActivity.getCell(1, 1);
final AtomicReference<AutofillId> fieldId = new AtomicReference<>();
sReplier.addResponse(new CannedFillResponse.Builder()
.setFieldClassificationIds(ID_L1C1)
.setVisitor((contexts, builder) -> fieldId
.set(findAutofillIdByResourceId(contexts.get(0), ID_L1C1)))
.build());
// Trigger autofill
mActivity.focusCell(1, 1);
sReplier.getNextFillRequest();
mUiBot.assertNoDatasetsEver();
callback.assertUiUnavailableEvent(field);
// Simulate user input
mActivity.setText(1, 1, "fully");
// Finish context.
mAfm.commit();
// Assert results
final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
assertFillEventForFieldsClassification(events.get(0), fieldId.get(), "myId", 1);
}
@Test
public void testHit_sameValueForMultipleCategories() throws Exception {
// Set service.
enableService();
// Set expectations.
mAfm.setUserData(new UserData
.Builder("id", "FULLY", "cat1")
.add("FULLY", "cat2")
.build());
final MyAutofillCallback callback = mActivity.registerCallback();
final EditText field = mActivity.getCell(1, 1);
final AtomicReference<AutofillId> fieldId = new AtomicReference<>();
sReplier.addResponse(new CannedFillResponse.Builder()
.setFieldClassificationIds(ID_L1C1)
.setVisitor((contexts, builder) -> fieldId
.set(findAutofillIdByResourceId(contexts.get(0), ID_L1C1)))
.build());
// Trigger autofill
mActivity.focusCell(1, 1);
sReplier.getNextFillRequest();
mUiBot.assertNoDatasetsEver();
callback.assertUiUnavailableEvent(field);
// Simulate user input
mActivity.setText(1, 1, "fully");
// Finish context.
mAfm.commit();
// Assert results
final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
assertFillEventForFieldsClassification(events.get(0),
new FieldClassificationResult[] {
new FieldClassificationResult(fieldId.get(),
new String[] { "cat1", "cat2"},
new float[] {1, 1})
});
}
@Test
public void testHit_manyUserData_oneDetectableField_bestMatchIsFirst() throws Exception {
manyUserData_oneDetectableField(true);
}
@Test
public void testHit_manyUserData_oneDetectableField_bestMatchIsSecond() throws Exception {
manyUserData_oneDetectableField(false);
}
private void manyUserData_oneDetectableField(boolean firstMatch) throws Exception {
// Set service.
enableService();
// Set expectations.
mAfm.setUserData(new UserData.Builder("id", "Iam1ST", "1stId")
.add("Iam2ND", "2ndId").build());
final MyAutofillCallback callback = mActivity.registerCallback();
final EditText field = mActivity.getCell(1, 1);
final AtomicReference<AutofillId> fieldId = new AtomicReference<>();
sReplier.addResponse(new CannedFillResponse.Builder()
.setFieldClassificationIds(ID_L1C1)
.setVisitor((contexts, builder) -> fieldId
.set(findAutofillIdByResourceId(contexts.get(0), ID_L1C1)))
.build());
// Trigger autofill
mActivity.focusCell(1, 1);
sReplier.getNextFillRequest();
mUiBot.assertNoDatasetsEver();
callback.assertUiUnavailableEvent(field);
// Simulate user input
mActivity.setText(1, 1, firstMatch ? "IAM111" : "IAM222");
// Finish context.
mAfm.commit();
// Assert results
final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
// Best match is 0.66 (4 of 6), worst is 0.5 (3 of 6)
if (firstMatch) {
assertFillEventForFieldsClassification(events.get(0), new FieldClassificationResult[] {
new FieldClassificationResult(fieldId.get(), new String[] { "1stId", "2ndId" },
new float[] { 0.66F, 0.5F })});
} else {
assertFillEventForFieldsClassification(events.get(0), new FieldClassificationResult[] {
new FieldClassificationResult(fieldId.get(), new String[] { "2ndId", "1stId" },
new float[] { 0.66F, 0.5F }) });
}
}
@Test
public void testHit_oneUserData_manyDetectableFields() throws Exception {
// Set service.
enableService();
// Set expectations.
mAfm.setUserData(new UserData.Builder("id", "FULLY", "myId").build());
final MyAutofillCallback callback = mActivity.registerCallback();
final EditText field1 = mActivity.getCell(1, 1);
final AtomicReference<AutofillId> fieldId1 = new AtomicReference<>();
final AtomicReference<AutofillId> fieldId2 = new AtomicReference<>();
sReplier.addResponse(new CannedFillResponse.Builder()
.setFieldClassificationIds(ID_L1C1, ID_L1C2)
.setVisitor((contexts, builder) -> {
final FillContext context = contexts.get(0);
fieldId1.set(findAutofillIdByResourceId(context, ID_L1C1));
fieldId2.set(findAutofillIdByResourceId(context, ID_L1C2));
})
.build());
// Trigger autofill
mActivity.focusCell(1, 1);
sReplier.getNextFillRequest();
mUiBot.assertNoDatasetsEver();
callback.assertUiUnavailableEvent(field1);
// Simulate user input
mActivity.setText(1, 1, "fully"); // 100%
mActivity.setText(1, 2, "fooly"); // 60%
// Finish context.
mAfm.commit();
// Assert results
final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
assertFillEventForFieldsClassification(events.get(0),
new FieldClassificationResult[] {
new FieldClassificationResult(fieldId1.get(), "myId", 1.0F),
new FieldClassificationResult(fieldId2.get(), "myId", 0.6F),
});
}
@Test
public void testHit_manyUserData_manyDetectableFields() throws Exception {
// Set service.
enableService();
// Set expectations.
mAfm.setUserData(new UserData.Builder("id", "FULLY", "myId")
.add("ZZZZZZZZZZ", "totalMiss") // should not have matched any
.add("EMPTY", "otherId")
.build());
final MyAutofillCallback callback = mActivity.registerCallback();
final EditText field1 = mActivity.getCell(1, 1);
final AtomicReference<AutofillId> fieldId1 = new AtomicReference<>();
final AtomicReference<AutofillId> fieldId2 = new AtomicReference<>();
final AtomicReference<AutofillId> fieldId3 = new AtomicReference<>();
final AtomicReference<AutofillId> fieldId4 = new AtomicReference<>();
sReplier.addResponse(new CannedFillResponse.Builder()
.setFieldClassificationIds(ID_L1C1, ID_L1C2)
.setVisitor((contexts, builder) -> {
final FillContext context = contexts.get(0);
fieldId1.set(findAutofillIdByResourceId(context, ID_L1C1));
fieldId2.set(findAutofillIdByResourceId(context, ID_L1C2));
fieldId3.set(findAutofillIdByResourceId(context, ID_L2C1));
fieldId4.set(findAutofillIdByResourceId(context, ID_L2C2));
})
.build());
// Trigger autofill
mActivity.focusCell(1, 1);
sReplier.getNextFillRequest();
mUiBot.assertNoDatasetsEver();
callback.assertUiUnavailableEvent(field1);
// Simulate user input
mActivity.setText(1, 1, "fully"); // u1: 100% u2: 20%
mActivity.setText(1, 2, "empty"); // u1: 20% u2: 100%
mActivity.setText(2, 1, "fooly"); // u1: 60% u2: 20%
mActivity.setText(2, 2, "emppy"); // u1: 20% u2: 80%
// Finish context.
mAfm.commit();
// Assert results
final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
assertFillEventForFieldsClassification(events.get(0),
new FieldClassificationResult[] {
new FieldClassificationResult(fieldId1.get(),
new String[] { "myId", "otherId" }, new float[] { 1.0F, 0.2F }),
new FieldClassificationResult(fieldId2.get(),
new String[] { "otherId", "myId" }, new float[] { 1.0F, 0.2F }),
new FieldClassificationResult(fieldId3.get(),
new String[] { "myId", "otherId" }, new float[] { 0.6F, 0.2F }),
new FieldClassificationResult(fieldId4.get(),
new String[] { "otherId", "myId"}, new float[] { 0.80F, 0.2F })});
}
@Test
public void testHit_manyUserData_manyDetectableFields_differentClassificationAlgo()
throws Exception {
// Set service.
enableService();
// Set expectations.
mAfm.setUserData(new UserData.Builder("id", "1234", "myId")
.add("ZZZZZZZZZZ", "totalMiss") // should not have matched any
.add("EMPTY", "otherId")
.setFieldClassificationAlgorithmForCategory("myId",
REQUIRED_ALGORITHM_EXACT_MATCH, mLast4Bundle)
.setFieldClassificationAlgorithmForCategory("otherId",
REQUIRED_ALGORITHM_EDIT_DISTANCE, null)
.build());
final MyAutofillCallback callback = mActivity.registerCallback();
final EditText field1 = mActivity.getCell(1, 1);
final AtomicReference<AutofillId> fieldId1 = new AtomicReference<>();
final AtomicReference<AutofillId> fieldId2 = new AtomicReference<>();
final AtomicReference<AutofillId> fieldId3 = new AtomicReference<>();
sReplier.addResponse(new CannedFillResponse.Builder()
.setFieldClassificationIds(ID_L1C1, ID_L1C2)
.setVisitor((contexts, builder) -> {
final FillContext context = contexts.get(0);
fieldId1.set(findAutofillIdByResourceId(context, ID_L1C1));
fieldId2.set(findAutofillIdByResourceId(context, ID_L1C2));
fieldId3.set(findAutofillIdByResourceId(context, ID_L2C1));
})
.build());
// Trigger autofill
mActivity.focusCell(1, 1);
sReplier.getNextFillRequest();
mUiBot.assertNoDatasetsEver();
callback.assertUiUnavailableEvent(field1);
// Simulate user input
mActivity.setText(1, 1, "E1234"); // u1: 100% u2: 20%
mActivity.setText(1, 2, "empty"); // u1: 0% u2: 100%
mActivity.setText(2, 1, "fULLy"); // u1: 0% u2: 20%
// Finish context.
mAfm.commit();
// Assert results
final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
assertFillEventForFieldsClassification(events.get(0),
new FieldClassificationResult[] {
new FieldClassificationResult(fieldId1.get(),
new String[] { "myId", "otherId" }, new float[] { 1.0F, 0.2F }),
new FieldClassificationResult(fieldId2.get(),
new String[] { "otherId" }, new float[] { 1.0F }),
new FieldClassificationResult(fieldId3.get(),
new String[] { "otherId" }, new float[] { 0.2F })});
}
@Test
public void testHit_manyUserDataPerField_manyDetectableFields() throws Exception {
// Set service.
enableService();
// Set expectations.
mAfm.setUserData(new UserData.Builder("id", "zzzzz", "myId") // should not have matched any
.add("FULL1", "myId") // match 80%, should not have been reported
.add("FULLY", "myId") // match 100%
.add("ZZZZZZZZZZ", "totalMiss") // should not have matched any
.add("EMPTY", "otherId")
.build());
final MyAutofillCallback callback = mActivity.registerCallback();
final EditText field1 = mActivity.getCell(1, 1);
final AtomicReference<AutofillId> fieldId1 = new AtomicReference<>();
final AtomicReference<AutofillId> fieldId2 = new AtomicReference<>();
final AtomicReference<AutofillId> fieldId3 = new AtomicReference<>();
final AtomicReference<AutofillId> fieldId4 = new AtomicReference<>();
sReplier.addResponse(new CannedFillResponse.Builder()
.setFieldClassificationIds(ID_L1C1, ID_L1C2)
.setVisitor((contexts, builder) -> {
final FillContext context = contexts.get(0);
fieldId1.set(findAutofillIdByResourceId(context, ID_L1C1));
fieldId2.set(findAutofillIdByResourceId(context, ID_L1C2));
fieldId3.set(findAutofillIdByResourceId(context, ID_L2C1));
fieldId4.set(findAutofillIdByResourceId(context, ID_L2C2));
})
.build());
// Trigger autofill
mActivity.focusCell(1, 1);
sReplier.getNextFillRequest();
mUiBot.assertNoDatasetsEver();
callback.assertUiUnavailableEvent(field1);
// Simulate user input
mActivity.setText(1, 1, "fully"); // u1: 100% u2: 20%
mActivity.setText(1, 2, "empty"); // u1: 20% u2: 100%
mActivity.setText(2, 1, "fooly"); // u1: 60% u2: 20%
mActivity.setText(2, 2, "emppy"); // u1: 20% u2: 80%
// Finish context.
mAfm.commit();
// Assert results
final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
assertFillEventForFieldsClassification(events.get(0),
new FieldClassificationResult[] {
new FieldClassificationResult(fieldId1.get(),
new String[] { "myId", "otherId" }, new float[] { 1.0F, 0.2F }),
new FieldClassificationResult(fieldId2.get(),
new String[] { "otherId", "myId" }, new float[] { 1.0F, 0.2F }),
new FieldClassificationResult(fieldId3.get(),
new String[] { "myId", "otherId" }, new float[] { 0.6F, 0.2F }),
new FieldClassificationResult(fieldId4.get(),
new String[] { "otherId", "myId"}, new float[] { 0.80F, 0.2F })});
}
@Test
public void testMiss() throws Exception {
// Set service.
enableService();
// Set expectations.
mAfm.setUserData(new UserData.Builder("id", "ABCDEF", "myId").build());
final MyAutofillCallback callback = mActivity.registerCallback();
final EditText field = mActivity.getCell(1, 1);
sReplier.addResponse(new CannedFillResponse.Builder()
.setFieldClassificationIds(ID_L1C1)
.build());
// Trigger autofill
mActivity.focusCell(1, 1);
sReplier.getNextFillRequest();
mUiBot.assertNoDatasetsEver();
callback.assertUiUnavailableEvent(field);
// Simulate user input
mActivity.setText(1, 1, "xyz");
// Finish context.
mAfm.commit();
// Assert results
final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
assertFillEventForContextCommitted(events.get(0));
}
@Test
public void testNoUserInput() throws Exception {
// Set service.
enableService();
// Set expectations.
mAfm.setUserData(new UserData.Builder("id", "FULLY", "myId").build());
final MyAutofillCallback callback = mActivity.registerCallback();
final EditText field = mActivity.getCell(1, 1);
sReplier.addResponse(new CannedFillResponse.Builder()
.setFieldClassificationIds(ID_L1C1)
.build());
// Trigger autofill
mActivity.focusCell(1, 1);
sReplier.getNextFillRequest();
mUiBot.assertNoDatasetsEver();
callback.assertUiUnavailableEvent(field);
// Finish context.
mAfm.commit();
// Assert results
final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
assertFillEventForContextCommitted(events.get(0));
}
@Test
public void testHit_usePackageUserData() throws Exception {
enableService();
// Set expectations.
mAfm.setUserData(new UserData
.Builder("id", "TEST1", "cat")
.setFieldClassificationAlgorithm(null, null)
.build());
final MyAutofillCallback callback = mActivity.registerCallback();
final EditText field = mActivity.getCell(1, 1);
final AtomicReference<AutofillId> fieldId1 = new AtomicReference<>();
sReplier.addResponse(new CannedFillResponse.Builder()
.setFieldClassificationIds(ID_L1C1)
.setVisitor((contexts, builder) -> fieldId1
.set(findAutofillIdByResourceId(contexts.get(0), ID_L1C1)))
.setUserData(new UserData.Builder("id2", "TEST2", "cat")
.setFieldClassificationAlgorithm(null, null)
.build())
.build());
// Trigger autofill
mActivity.focusCell(1, 1);
sReplier.getNextFillRequest();
mUiBot.assertNoDatasetsEver();
callback.assertUiUnavailableEvent(field);
// Simulate user input
mActivity.setText(1, 1, "test1");
// Finish context
mAfm.commit();
final Event packageUserDataEvent = InstrumentedAutoFillService.getFillEvents(1).get(0);
assertFillEventForFieldsClassification(packageUserDataEvent, fieldId1.get(), "cat", 0.8F);
final AtomicReference<AutofillId> fieldId2 = new AtomicReference<>();
sReplier.addResponse(new CannedFillResponse.Builder()
.setVisitor((contexts, builder) -> fieldId2
.set(findAutofillIdByResourceId(contexts.get(0), ID_L1C1)))
.setFieldClassificationIds(ID_L1C1)
.build());
// Need to switch focus first
mActivity.focusCell(1, 2);
// Trigger second autofill
mActivity.focusCell(1, 1);
sReplier.getNextFillRequest();
mUiBot.assertNoDatasetsEver();
callback.assertUiUnavailableEvent(field);
// Finish context.
mAfm.commit();
// Assert results
final Event defaultUserDataEvent = InstrumentedAutoFillService.getFillEvents(1).get(0);
assertFillEventForFieldsClassification(defaultUserDataEvent, fieldId2.get(), "cat", 1.0F);
}
@Test
public void testHit_mergeUserData_manyDetectableFields() throws Exception {
// Set service.
enableService();
// Set expectations.
mAfm.setUserData(new UserData.Builder("id", "FULLY", "myId").build());
final MyAutofillCallback callback = mActivity.registerCallback();
final EditText field1 = mActivity.getCell(1, 1);
final AtomicReference<AutofillId> fieldId1 = new AtomicReference<>();
final AtomicReference<AutofillId> fieldId2 = new AtomicReference<>();
sReplier.addResponse(new CannedFillResponse.Builder()
.setFieldClassificationIds(ID_L1C1, ID_L1C2)
.setVisitor((contexts, builder) -> {
final FillContext context = contexts.get(0);
fieldId1.set(findAutofillIdByResourceId(context, ID_L1C1));
fieldId2.set(findAutofillIdByResourceId(context, ID_L1C2));
})
.setUserData(new UserData.Builder("id2", "FOOLY", "otherId")
.add("EMPTY", "myId")
.build())
.build());
// Trigger autofill
mActivity.focusCell(1, 1);
sReplier.getNextFillRequest();
mUiBot.assertNoDatasetsEver();
callback.assertUiUnavailableEvent(field1);
// Simulate user input
mActivity.setText(1, 1, "fully"); // u1: 20%, u2: 60%
mActivity.setText(1, 2, "empty"); // u1: 100%, u2: 20%
// Finish context.
mAfm.commit();
// Assert results
final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
assertFillEventForFieldsClassification(events.get(0),
new FieldClassificationResult[] {
new FieldClassificationResult(fieldId1.get(),
new String[] { "otherId", "myId" }, new float[] { 0.6F, 0.2F }),
new FieldClassificationResult(fieldId2.get(),
new String[] { "myId", "otherId" }, new float[] { 1.0F, 0.2F }),
});
}
/*
* TODO(b/73648631): other scenarios:
*
* - Multipartition (for example, one response with FieldsDetection, others with datasets,
* saveinfo, and/or ignoredIds)
* - make sure detectable fields don't trigger a new partition
* v test partial hit (for example, 'fool' instead of 'full'
* v multiple fields
* v multiple value
* - combinations of above items
*/
}