Home | History | Annotate | Download | only in cts
      1 /*
      2  * Copyright 2017 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 package android.autofillservice.cts;
     17 
     18 import static android.autofillservice.cts.Helper.assertFillEventForContextCommitted;
     19 import static android.autofillservice.cts.Helper.assertFillEventForFieldsClassification;
     20 import static android.provider.Settings.Secure.AUTOFILL_FEATURE_FIELD_CLASSIFICATION;
     21 import static android.provider.Settings.Secure.AUTOFILL_USER_DATA_MAX_CATEGORY_COUNT;
     22 import static android.provider.Settings.Secure.AUTOFILL_USER_DATA_MAX_FIELD_CLASSIFICATION_IDS_SIZE;
     23 import static android.provider.Settings.Secure.AUTOFILL_USER_DATA_MAX_USER_DATA_SIZE;
     24 import static android.provider.Settings.Secure.AUTOFILL_USER_DATA_MAX_VALUE_LENGTH;
     25 import static android.provider.Settings.Secure.AUTOFILL_USER_DATA_MIN_VALUE_LENGTH;
     26 
     27 import static com.google.common.truth.Truth.assertThat;
     28 
     29 import android.autofillservice.cts.Helper.FieldClassificationResult;
     30 import android.autofillservice.cts.common.SettingsStateChangerRule;
     31 import android.content.Context;
     32 import android.platform.test.annotations.AppModeFull;
     33 import android.service.autofill.FillEventHistory.Event;
     34 import android.service.autofill.UserData;
     35 import android.support.test.InstrumentationRegistry;
     36 import android.view.autofill.AutofillId;
     37 import android.view.autofill.AutofillManager;
     38 import android.widget.EditText;
     39 
     40 import org.junit.Before;
     41 import org.junit.ClassRule;
     42 import org.junit.Rule;
     43 import org.junit.Test;
     44 
     45 import java.util.List;
     46 
     47 @AppModeFull // Service-specific test
     48 public class FieldsClassificationTest extends AutoFillServiceTestCase {
     49 
     50     private static final Context sContext = InstrumentationRegistry.getContext();
     51 
     52     @ClassRule
     53     public static final SettingsStateChangerRule sFeatureEnabler =
     54             new SettingsStateChangerRule(sContext, AUTOFILL_FEATURE_FIELD_CLASSIFICATION, "1");
     55 
     56     @ClassRule
     57     public static final SettingsStateChangerRule sUserDataMaxFcSizeChanger =
     58             new SettingsStateChangerRule(sContext,
     59                     AUTOFILL_USER_DATA_MAX_FIELD_CLASSIFICATION_IDS_SIZE, "10");
     60 
     61     @ClassRule
     62     public static final SettingsStateChangerRule sUserDataMaxUserSizeChanger =
     63             new SettingsStateChangerRule(sContext, AUTOFILL_USER_DATA_MAX_USER_DATA_SIZE, "9");
     64 
     65     @ClassRule
     66     public static final SettingsStateChangerRule sUserDataMinValueChanger =
     67             new SettingsStateChangerRule(sContext, AUTOFILL_USER_DATA_MIN_VALUE_LENGTH, "5");
     68 
     69     @ClassRule
     70     public static final SettingsStateChangerRule sUserDataMaxValueChanger =
     71             new SettingsStateChangerRule(sContext, AUTOFILL_USER_DATA_MAX_VALUE_LENGTH, "50");
     72 
     73     @ClassRule
     74     public static final SettingsStateChangerRule sUserDataMaxCategoryChanger =
     75             new SettingsStateChangerRule(sContext, AUTOFILL_USER_DATA_MAX_CATEGORY_COUNT, "42");
     76 
     77     @Rule
     78     public final AutofillActivityTestRule<GridActivity> mActivityRule =
     79             new AutofillActivityTestRule<GridActivity>(GridActivity.class);
     80 
     81 
     82     private GridActivity mActivity;
     83     private AutofillManager mAfm;
     84 
     85     @Before
     86     public void setFixtures() {
     87         mActivity = mActivityRule.getActivity();
     88         mAfm = mActivity.getAutofillManager();
     89     }
     90 
     91     @Test
     92     public void testFeatureIsEnabled() throws Exception {
     93         enableService();
     94         assertThat(mAfm.isFieldClassificationEnabled()).isTrue();
     95 
     96         disableService();
     97         assertThat(mAfm.isFieldClassificationEnabled()).isFalse();
     98     }
     99 
    100     @Test
    101     public void testGetAlgorithm() throws Exception {
    102         enableService();
    103 
    104         // Check algorithms
    105         final List<String> names = mAfm.getAvailableFieldClassificationAlgorithms();
    106         assertThat(names.size()).isAtLeast(1);
    107         final String defaultAlgorithm = getDefaultAlgorithm();
    108         assertThat(defaultAlgorithm).isNotEmpty();
    109         assertThat(names).contains(defaultAlgorithm);
    110 
    111         // Checks invalid service
    112         disableService();
    113         assertThat(mAfm.getAvailableFieldClassificationAlgorithms()).isEmpty();
    114     }
    115 
    116     @Test
    117     public void testUserData() throws Exception {
    118         assertThat(mAfm.getUserData()).isNull();
    119         assertThat(mAfm.getUserDataId()).isNull();
    120 
    121         enableService();
    122         mAfm.setUserData(new UserData.Builder("user_data_id", "value", "remote_id")
    123                 .build());
    124         assertThat(mAfm.getUserDataId()).isEqualTo("user_data_id");
    125         final UserData userData = mAfm.getUserData();
    126         assertThat(userData.getId()).isEqualTo("user_data_id");
    127         assertThat(userData.getFieldClassificationAlgorithm()).isNull();
    128 
    129         disableService();
    130         assertThat(mAfm.getUserData()).isNull();
    131         assertThat(mAfm.getUserDataId()).isNull();
    132     }
    133 
    134     @Test
    135     public void testUserDataConstraints() throws Exception {
    136         // NOTE: values set by the SettingsStateChangerRule @Rules should have unique values to
    137         // make sure the getters below are reading the right property.
    138         assertThat(UserData.getMaxFieldClassificationIdsSize()).isEqualTo(10);
    139         assertThat(UserData.getMaxUserDataSize()).isEqualTo(9);
    140         assertThat(UserData.getMinValueLength()).isEqualTo(5);
    141         assertThat(UserData.getMaxValueLength()).isEqualTo(50);
    142         assertThat(UserData.getMaxCategoryCount()).isEqualTo(42);
    143     }
    144 
    145     @Test
    146     public void testHit_oneUserData_oneDetectableField() throws Exception {
    147         simpleHitTest(false, null);
    148     }
    149 
    150     @Test
    151     public void testHit_invalidAlgorithmIsIgnored() throws Exception {
    152         // For simplicity's sake, let's assume that name will never be valid..
    153         String invalidName = " ALGORITHM, Y NO INVALID? ";
    154 
    155         simpleHitTest(true, invalidName);
    156     }
    157 
    158     @Test
    159     public void testHit_userDataAlgorithmIsReset() throws Exception {
    160         simpleHitTest(true, null);
    161     }
    162 
    163     private void simpleHitTest(boolean setAlgorithm, String algorithm) throws Exception {
    164         // Set service.
    165         enableService();
    166 
    167         // Set expectations.
    168         final UserData.Builder userData = new UserData.Builder("id", "FULLY", "myId");
    169         if (setAlgorithm) {
    170             userData.setFieldClassificationAlgorithm(algorithm, null);
    171         }
    172         mAfm.setUserData(userData.build());
    173         final MyAutofillCallback callback = mActivity.registerCallback();
    174         final EditText field = mActivity.getCell(1, 1);
    175         final AutofillId fieldId = field.getAutofillId();
    176         sReplier.addResponse(new CannedFillResponse.Builder()
    177                 .setFieldClassificationIds(fieldId)
    178                 .build());
    179 
    180         // Trigger autofill
    181         mActivity.focusCell(1, 1);
    182         sReplier.getNextFillRequest();
    183 
    184         mUiBot.assertNoDatasetsEver();
    185         callback.assertUiUnavailableEvent(field);
    186 
    187         // Simulate user input
    188         mActivity.setText(1, 1, "fully");
    189 
    190         // Finish context.
    191         mAfm.commit();
    192 
    193         // Assert results
    194         final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
    195         assertFillEventForFieldsClassification(events.get(0), fieldId, "myId", 1);
    196     }
    197 
    198     @Test
    199     public void testHit_manyUserData_oneDetectableField_bestMatchIsFirst() throws Exception {
    200         manyUserData_oneDetectableField(true);
    201     }
    202 
    203     @Test
    204     public void testHit_manyUserData_oneDetectableField_bestMatchIsSecond() throws Exception {
    205         manyUserData_oneDetectableField(false);
    206     }
    207 
    208     private void manyUserData_oneDetectableField(boolean firstMatch) throws Exception {
    209         // Set service.
    210         enableService();
    211 
    212         // Set expectations.
    213         mAfm.setUserData(new UserData.Builder("id", "Iam1ST", "1stId")
    214                 .add("Iam2ND", "2ndId").build());
    215         final MyAutofillCallback callback = mActivity.registerCallback();
    216         final EditText field = mActivity.getCell(1, 1);
    217         final AutofillId fieldId = field.getAutofillId();
    218         sReplier.addResponse(new CannedFillResponse.Builder()
    219                 .setFieldClassificationIds(fieldId)
    220                 .build());
    221 
    222         // Trigger autofill
    223         mActivity.focusCell(1, 1);
    224         sReplier.getNextFillRequest();
    225 
    226         mUiBot.assertNoDatasetsEver();
    227         callback.assertUiUnavailableEvent(field);
    228 
    229         // Simulate user input
    230         mActivity.setText(1, 1, firstMatch ? "IAM111" : "IAM222");
    231 
    232         // Finish context.
    233         mAfm.commit();
    234 
    235         // Assert results
    236         final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
    237         // Best match is 0.66 (4 of 6), worst is 0.5 (3 of 6)
    238         if (firstMatch) {
    239             assertFillEventForFieldsClassification(events.get(0), new FieldClassificationResult[] {
    240                     new FieldClassificationResult(fieldId, new String[] { "1stId", "2ndId" },
    241                             new float[] { 0.66F, 0.5F })});
    242         } else {
    243             assertFillEventForFieldsClassification(events.get(0), new FieldClassificationResult[] {
    244                     new FieldClassificationResult(fieldId, new String[] { "2ndId", "1stId" },
    245                             new float[] { 0.66F, 0.5F }) });
    246         }
    247     }
    248 
    249     @Test
    250     public void testHit_oneUserData_manyDetectableFields() throws Exception {
    251         // Set service.
    252         enableService();
    253 
    254         // Set expectations.
    255         mAfm.setUserData(new UserData.Builder("id", "FULLY", "myId").build());
    256         final MyAutofillCallback callback = mActivity.registerCallback();
    257         final EditText field1 = mActivity.getCell(1, 1);
    258         final AutofillId fieldId1 = field1.getAutofillId();
    259         final EditText field2 = mActivity.getCell(1, 2);
    260         final AutofillId fieldId2 = field2.getAutofillId();
    261         sReplier.addResponse(new CannedFillResponse.Builder()
    262                 .setFieldClassificationIds(fieldId1, fieldId2)
    263                 .build());
    264 
    265         // Trigger autofill
    266         mActivity.focusCell(1, 1);
    267         sReplier.getNextFillRequest();
    268 
    269         mUiBot.assertNoDatasetsEver();
    270         callback.assertUiUnavailableEvent(field1);
    271 
    272         // Simulate user input
    273         mActivity.setText(1, 1, "fully"); // 100%
    274         mActivity.setText(1, 2, "fooly"); // 60%
    275 
    276         // Finish context.
    277         mAfm.commit();
    278 
    279         // Assert results
    280         final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
    281         assertFillEventForFieldsClassification(events.get(0),
    282                 new FieldClassificationResult[] {
    283                         new FieldClassificationResult(fieldId1, "myId", 1.0F),
    284                         new FieldClassificationResult(fieldId2, "myId", 0.6F),
    285                 });
    286     }
    287 
    288     @Test
    289     public void testHit_manyUserData_manyDetectableFields() throws Exception {
    290         // Set service.
    291         enableService();
    292 
    293         // Set expectations.
    294         mAfm.setUserData(new UserData.Builder("id", "FULLY", "myId")
    295                 .add("ZZZZZZZZZZ", "totalMiss") // should not have matched any
    296                 .add("EMPTY", "otherId")
    297                 .build());
    298         final MyAutofillCallback callback = mActivity.registerCallback();
    299         final EditText field1 = mActivity.getCell(1, 1);
    300         final AutofillId fieldId1 = field1.getAutofillId();
    301         final EditText field2 = mActivity.getCell(1, 2);
    302         final AutofillId fieldId2 = field2.getAutofillId();
    303         final EditText field3 = mActivity.getCell(2, 1);
    304         final AutofillId fieldId3 = field3.getAutofillId();
    305         final EditText field4 = mActivity.getCell(2, 2);
    306         final AutofillId fieldId4 = field4.getAutofillId();
    307         sReplier.addResponse(new CannedFillResponse.Builder()
    308                 .setFieldClassificationIds(fieldId1, fieldId2)
    309                 .build());
    310 
    311         // Trigger autofill
    312         mActivity.focusCell(1, 1);
    313         sReplier.getNextFillRequest();
    314 
    315         mUiBot.assertNoDatasetsEver();
    316         callback.assertUiUnavailableEvent(field1);
    317 
    318         // Simulate user input
    319         mActivity.setText(1, 1, "fully"); // u1: 100% u2:  20%
    320         mActivity.setText(1, 2, "empty"); // u1:  20% u2: 100%
    321         mActivity.setText(2, 1, "fooly"); // u1:  60% u2:  20%
    322         mActivity.setText(2, 2, "emppy"); // u1:  20% u2:  80%
    323 
    324         // Finish context.
    325         mAfm.commit();
    326 
    327         // Assert results
    328         final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
    329         assertFillEventForFieldsClassification(events.get(0),
    330                 new FieldClassificationResult[] {
    331                         new FieldClassificationResult(fieldId1, new String[] { "myId", "otherId" },
    332                                 new float[] { 1.0F, 0.2F }),
    333                         new FieldClassificationResult(fieldId2, new String[] { "otherId", "myId" },
    334                                 new float[] { 1.0F, 0.2F }),
    335                         new FieldClassificationResult(fieldId3, new String[] { "myId", "otherId" },
    336                                 new float[] { 0.6F, 0.2F }),
    337                         new FieldClassificationResult(fieldId4, new String[] { "otherId", "myId"},
    338                                 new float[] { 0.80F, 0.2F })});
    339     }
    340 
    341     @Test
    342     public void testHit_manyUserDataPerField_manyDetectableFields() throws Exception {
    343         // Set service.
    344         enableService();
    345 
    346         // Set expectations.
    347         mAfm.setUserData(new UserData.Builder("id", "zzzzz", "myId") // should not have matched any
    348                 .add("FULL1", "myId") // match 80%, should not have been reported
    349                 .add("FULLY", "myId") // match 100%
    350                 .add("ZZZZZZZZZZ", "totalMiss") // should not have matched any
    351                 .add("EMPTY", "otherId")
    352                 .build());
    353         final MyAutofillCallback callback = mActivity.registerCallback();
    354         final EditText field1 = mActivity.getCell(1, 1);
    355         final AutofillId fieldId1 = field1.getAutofillId();
    356         final EditText field2 = mActivity.getCell(1, 2);
    357         final AutofillId fieldId2 = field2.getAutofillId();
    358         final EditText field3 = mActivity.getCell(2, 1);
    359         final AutofillId fieldId3 = field3.getAutofillId();
    360         final EditText field4 = mActivity.getCell(2, 2);
    361         final AutofillId fieldId4 = field4.getAutofillId();
    362         sReplier.addResponse(new CannedFillResponse.Builder()
    363                 .setFieldClassificationIds(fieldId1, fieldId2)
    364                 .build());
    365 
    366         // Trigger autofill
    367         mActivity.focusCell(1, 1);
    368         sReplier.getNextFillRequest();
    369 
    370         mUiBot.assertNoDatasetsEver();
    371         callback.assertUiUnavailableEvent(field1);
    372 
    373         // Simulate user input
    374         mActivity.setText(1, 1, "fully"); // u1: 100% u2:  20%
    375         mActivity.setText(1, 2, "empty"); // u1:  20% u2: 100%
    376         mActivity.setText(2, 1, "fooly"); // u1:  60% u2:  20%
    377         mActivity.setText(2, 2, "emppy"); // u1:  20% u2:  80%
    378 
    379         // Finish context.
    380         mAfm.commit();
    381 
    382         // Assert results
    383         final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
    384         assertFillEventForFieldsClassification(events.get(0),
    385                 new FieldClassificationResult[] {
    386                         new FieldClassificationResult(fieldId1, new String[] { "myId", "otherId" },
    387                                 new float[] { 1.0F, 0.2F }),
    388                         new FieldClassificationResult(fieldId2, new String[] { "otherId", "myId" },
    389                                 new float[] { 1.0F, 0.2F }),
    390                         new FieldClassificationResult(fieldId3, new String[] { "myId", "otherId" },
    391                                 new float[] { 0.6F, 0.2F }),
    392                         new FieldClassificationResult(fieldId4, new String[] { "otherId", "myId"},
    393                                 new float[] { 0.80F, 0.2F })});
    394     }
    395 
    396     @Test
    397     public void testMiss() throws Exception {
    398         // Set service.
    399         enableService();
    400 
    401         // Set expectations.
    402         mAfm.setUserData(new UserData.Builder("id", "ABCDEF", "myId").build());
    403         final MyAutofillCallback callback = mActivity.registerCallback();
    404         final EditText field = mActivity.getCell(1, 1);
    405         final AutofillId fieldId = field.getAutofillId();
    406         sReplier.addResponse(new CannedFillResponse.Builder()
    407                 .setFieldClassificationIds(fieldId)
    408                 .build());
    409 
    410         // Trigger autofill
    411         mActivity.focusCell(1, 1);
    412         sReplier.getNextFillRequest();
    413 
    414         mUiBot.assertNoDatasetsEver();
    415         callback.assertUiUnavailableEvent(field);
    416 
    417         // Simulate user input
    418         mActivity.setText(1, 1, "xyz");
    419 
    420         // Finish context.
    421         mAfm.commit();
    422 
    423         // Assert results
    424         final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
    425         assertFillEventForContextCommitted(events.get(0));
    426     }
    427 
    428     @Test
    429     public void testNoUserInput() throws Exception {
    430         // Set service.
    431         enableService();
    432 
    433         // Set expectations.
    434         mAfm.setUserData(new UserData.Builder("id", "FULLY", "myId").build());
    435         final MyAutofillCallback callback = mActivity.registerCallback();
    436         final EditText field = mActivity.getCell(1, 1);
    437         final AutofillId fieldId = field.getAutofillId();
    438         sReplier.addResponse(new CannedFillResponse.Builder()
    439                 .setFieldClassificationIds(fieldId)
    440                 .build());
    441 
    442         // Trigger autofill
    443         mActivity.focusCell(1, 1);
    444         sReplier.getNextFillRequest();
    445 
    446         mUiBot.assertNoDatasetsEver();
    447         callback.assertUiUnavailableEvent(field);
    448 
    449         // Finish context.
    450         mAfm.commit();
    451 
    452         // Assert results
    453         final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
    454         assertFillEventForContextCommitted(events.get(0));
    455     }
    456 
    457     private String getDefaultAlgorithm() {
    458         return mAfm.getDefaultFieldClassificationAlgorithm();
    459     }
    460 
    461     /*
    462      * TODO(b/73648631): other scenarios:
    463      *
    464      * - Multipartition (for example, one response with FieldsDetection, others with datasets,
    465      *   saveinfo, and/or ignoredIds)
    466      * - make sure detectable fields don't trigger a new partition
    467      * v test partial hit (for example, 'fool' instead of 'full'
    468      * v multiple fields
    469      * v multiple value
    470      * - combinations of above items
    471      */
    472 }
    473