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
setFixtures()86     public void setFixtures() {
87         mActivity = mActivityRule.getActivity();
88         mAfm = mActivity.getAutofillManager();
89     }
90 
91     @Test
testFeatureIsEnabled()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
testGetAlgorithm()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
testUserData()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
testUserDataConstraints()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
testHit_oneUserData_oneDetectableField()146     public void testHit_oneUserData_oneDetectableField() throws Exception {
147         simpleHitTest(false, null);
148     }
149 
150     @Test
testHit_invalidAlgorithmIsIgnored()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
testHit_userDataAlgorithmIsReset()159     public void testHit_userDataAlgorithmIsReset() throws Exception {
160         simpleHitTest(true, null);
161     }
162 
simpleHitTest(boolean setAlgorithm, String algorithm)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
testHit_manyUserData_oneDetectableField_bestMatchIsFirst()199     public void testHit_manyUserData_oneDetectableField_bestMatchIsFirst() throws Exception {
200         manyUserData_oneDetectableField(true);
201     }
202 
203     @Test
testHit_manyUserData_oneDetectableField_bestMatchIsSecond()204     public void testHit_manyUserData_oneDetectableField_bestMatchIsSecond() throws Exception {
205         manyUserData_oneDetectableField(false);
206     }
207 
manyUserData_oneDetectableField(boolean firstMatch)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
testHit_oneUserData_manyDetectableFields()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
testHit_manyUserData_manyDetectableFields()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
testHit_manyUserDataPerField_manyDetectableFields()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
testMiss()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
testNoUserInput()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 
getDefaultAlgorithm()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