blob: 6d7a914c39599dab27328856a53fffab9d34cdf7 [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.Helper.assertFillEventForContextCommitted;
import static android.autofillservice.cts.Helper.assertFillEventForFieldsClassification;
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 com.google.common.truth.Truth.assertThat;
import android.autofillservice.cts.Helper.FieldClassificationResult;
import android.autofillservice.cts.common.SettingsStateChangerRule;
import android.content.Context;
import android.platform.test.annotations.AppModeFull;
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 androidx.test.InstrumentationRegistry;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import java.util.List;
@AppModeFull // Service-specific test
public class FieldsClassificationTest extends AutoFillServiceTestCase {
private static final Context sContext = InstrumentationRegistry.getContext();
@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, "5");
@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");
@Rule
public final AutofillActivityTestRule<GridActivity> mActivityRule =
new AutofillActivityTestRule<GridActivity>(GridActivity.class);
private GridActivity mActivity;
private AutofillManager mAfm;
@Before
public void setFixtures() {
mActivity = mActivityRule.getActivity();
mAfm = mActivity.getAutofillManager();
}
@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 = getDefaultAlgorithm();
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.getUserDataId()).isEqualTo("user_data_id");
final UserData userData = mAfm.getUserData();
assertThat(userData.getId()).isEqualTo("user_data_id");
assertThat(userData.getFieldClassificationAlgorithm()).isNull();
disableService();
assertThat(mAfm.getUserData()).isNull();
assertThat(mAfm.getUserDataId()).isNull();
}
@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(5);
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);
}
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 AutofillId fieldId = field.getAutofillId();
sReplier.addResponse(new CannedFillResponse.Builder()
.setFieldClassificationIds(fieldId)
.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, "myId", 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 AutofillId fieldId = field.getAutofillId();
sReplier.addResponse(new CannedFillResponse.Builder()
.setFieldClassificationIds(fieldId)
.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, new String[] { "1stId", "2ndId" },
new float[] { 0.66F, 0.5F })});
} else {
assertFillEventForFieldsClassification(events.get(0), new FieldClassificationResult[] {
new FieldClassificationResult(fieldId, 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 AutofillId fieldId1 = field1.getAutofillId();
final EditText field2 = mActivity.getCell(1, 2);
final AutofillId fieldId2 = field2.getAutofillId();
sReplier.addResponse(new CannedFillResponse.Builder()
.setFieldClassificationIds(fieldId1, fieldId2)
.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, "myId", 1.0F),
new FieldClassificationResult(fieldId2, "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 AutofillId fieldId1 = field1.getAutofillId();
final EditText field2 = mActivity.getCell(1, 2);
final AutofillId fieldId2 = field2.getAutofillId();
final EditText field3 = mActivity.getCell(2, 1);
final AutofillId fieldId3 = field3.getAutofillId();
final EditText field4 = mActivity.getCell(2, 2);
final AutofillId fieldId4 = field4.getAutofillId();
sReplier.addResponse(new CannedFillResponse.Builder()
.setFieldClassificationIds(fieldId1, fieldId2)
.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, new String[] { "myId", "otherId" },
new float[] { 1.0F, 0.2F }),
new FieldClassificationResult(fieldId2, new String[] { "otherId", "myId" },
new float[] { 1.0F, 0.2F }),
new FieldClassificationResult(fieldId3, new String[] { "myId", "otherId" },
new float[] { 0.6F, 0.2F }),
new FieldClassificationResult(fieldId4, new String[] { "otherId", "myId"},
new float[] { 0.80F, 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 AutofillId fieldId1 = field1.getAutofillId();
final EditText field2 = mActivity.getCell(1, 2);
final AutofillId fieldId2 = field2.getAutofillId();
final EditText field3 = mActivity.getCell(2, 1);
final AutofillId fieldId3 = field3.getAutofillId();
final EditText field4 = mActivity.getCell(2, 2);
final AutofillId fieldId4 = field4.getAutofillId();
sReplier.addResponse(new CannedFillResponse.Builder()
.setFieldClassificationIds(fieldId1, fieldId2)
.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, new String[] { "myId", "otherId" },
new float[] { 1.0F, 0.2F }),
new FieldClassificationResult(fieldId2, new String[] { "otherId", "myId" },
new float[] { 1.0F, 0.2F }),
new FieldClassificationResult(fieldId3, new String[] { "myId", "otherId" },
new float[] { 0.6F, 0.2F }),
new FieldClassificationResult(fieldId4, 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);
final AutofillId fieldId = field.getAutofillId();
sReplier.addResponse(new CannedFillResponse.Builder()
.setFieldClassificationIds(fieldId)
.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);
final AutofillId fieldId = field.getAutofillId();
sReplier.addResponse(new CannedFillResponse.Builder()
.setFieldClassificationIds(fieldId)
.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));
}
private String getDefaultAlgorithm() {
return mAfm.getDefaultFieldClassificationAlgorithm();
}
/*
* 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
*/
}