1 /*
2  * Copyright (C) 2018 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 
17 package android.autofillservice.cts;
18 
19 import static android.autofillservice.cts.Helper.ID_USERNAME;
20 import static android.autofillservice.cts.Timeouts.MOCK_IME_TIMEOUT_MS;
21 
22 import static com.android.compatibility.common.util.ShellUtils.sendKeyEvent;
23 import static com.android.cts.mockime.ImeEventStreamTestUtils.editorMatcher;
24 import static com.android.cts.mockime.ImeEventStreamTestUtils.expectBindInput;
25 import static com.android.cts.mockime.ImeEventStreamTestUtils.expectCommand;
26 import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEvent;
27 
28 import android.autofillservice.cts.CannedFillResponse.CannedDataset;
29 import android.content.IntentSender;
30 import android.os.Process;
31 import android.platform.test.annotations.AppModeFull;
32 import android.view.KeyEvent;
33 import android.view.View;
34 import android.widget.EditText;
35 
36 import com.android.cts.mockime.ImeCommand;
37 import com.android.cts.mockime.ImeEventStream;
38 import com.android.cts.mockime.MockImeSession;
39 
40 import org.junit.AfterClass;
41 import org.junit.BeforeClass;
42 import org.junit.Test;
43 
44 import java.util.regex.Pattern;
45 
46 public class DatasetFilteringTest extends AbstractLoginActivityTestCase {
47 
48     @BeforeClass
setMaxDatasets()49     public static void setMaxDatasets() throws Exception {
50         Helper.setMaxVisibleDatasets(4);
51     }
52 
53     @AfterClass
restoreMaxDatasets()54     public static void restoreMaxDatasets() throws Exception {
55         Helper.setMaxVisibleDatasets(0);
56     }
57 
changeUsername(CharSequence username)58     private void changeUsername(CharSequence username) {
59         mActivity.onUsername((v) -> v.setText(username));
60     }
61 
62 
63     @Test
testFilter()64     public void testFilter() throws Exception {
65         final String aa = "Two A's";
66         final String ab = "A and B";
67         final String b = "Only B";
68 
69         enableService();
70 
71         // Set expectations.
72         sReplier.addResponse(new CannedFillResponse.Builder()
73                 .addDataset(new CannedDataset.Builder()
74                         .setField(ID_USERNAME, "aa")
75                         .setPresentation(createPresentation(aa))
76                         .build())
77                 .addDataset(new CannedDataset.Builder()
78                         .setField(ID_USERNAME, "ab")
79                         .setPresentation(createPresentation(ab))
80                         .build())
81                 .addDataset(new CannedDataset.Builder()
82                         .setField(ID_USERNAME, "b")
83                         .setPresentation(createPresentation(b))
84                         .build())
85                 .build());
86 
87         // Trigger auto-fill.
88         requestFocusOnUsername();
89         sReplier.getNextFillRequest();
90 
91         // With no filter text all datasets should be shown
92         mUiBot.assertDatasets(aa, ab, b);
93 
94         // Only two datasets start with 'a'
95         changeUsername("a");
96         mUiBot.assertDatasets(aa, ab);
97 
98         // Only one dataset start with 'aa'
99         changeUsername("aa");
100         mUiBot.assertDatasets(aa);
101 
102         // Only two datasets start with 'a'
103         changeUsername("a");
104         mUiBot.assertDatasets(aa, ab);
105 
106         // With no filter text all datasets should be shown
107         changeUsername("");
108         mUiBot.assertDatasets(aa, ab, b);
109 
110         // No dataset start with 'aaa'
111         final MyAutofillCallback callback = mActivity.registerCallback();
112         changeUsername("aaa");
113         callback.assertUiHiddenEvent(mActivity.getUsername());
114         mUiBot.assertNoDatasets();
115     }
116 
117     @Test
testFilter_injectingEvents()118     public void testFilter_injectingEvents() throws Exception {
119         final String aa = "Two A's";
120         final String ab = "A and B";
121         final String b = "Only B";
122 
123         enableService();
124 
125         // Set expectations.
126         sReplier.addResponse(new CannedFillResponse.Builder()
127                 .addDataset(new CannedDataset.Builder()
128                         .setField(ID_USERNAME, "aa")
129                         .setPresentation(createPresentation(aa))
130                         .build())
131                 .addDataset(new CannedDataset.Builder()
132                         .setField(ID_USERNAME, "ab")
133                         .setPresentation(createPresentation(ab))
134                         .build())
135                 .addDataset(new CannedDataset.Builder()
136                         .setField(ID_USERNAME, "b")
137                         .setPresentation(createPresentation(b))
138                         .build())
139                 .build());
140 
141         // Trigger auto-fill.
142         requestFocusOnUsername();
143         sReplier.getNextFillRequest();
144 
145         // With no filter text all datasets should be shown
146         mUiBot.assertDatasets(aa, ab, b);
147 
148         // Only two datasets start with 'a'
149         sendKeyEvent("KEYCODE_A");
150         mUiBot.assertDatasets(aa, ab);
151 
152         // Only one dataset start with 'aa'
153         sendKeyEvent("KEYCODE_A");
154         mUiBot.assertDatasets(aa);
155 
156         // Only two datasets start with 'a'
157         sendKeyEvent("KEYCODE_DEL");
158         mUiBot.assertDatasets(aa, ab);
159 
160         // With no filter text all datasets should be shown
161         sendKeyEvent("KEYCODE_DEL");
162         mUiBot.assertDatasets(aa, ab, b);
163 
164         // No dataset start with 'aaa'
165         final MyAutofillCallback callback = mActivity.registerCallback();
166         sendKeyEvent("KEYCODE_A");
167         sendKeyEvent("KEYCODE_A");
168         sendKeyEvent("KEYCODE_A");
169         callback.assertUiHiddenEvent(mActivity.getUsername());
170         mUiBot.assertNoDatasets();
171     }
172 
173     @Test
testFilter_usingKeyboard()174     public void testFilter_usingKeyboard() throws Exception {
175         final String aa = "Two A's";
176         final String ab = "A and B";
177         final String b = "Only B";
178 
179         final MockImeSession mockImeSession = sMockImeSessionRule.getMockImeSession();
180 
181         enableService();
182 
183         // Set expectations.
184         sReplier.addResponse(new CannedFillResponse.Builder()
185                 .addDataset(new CannedDataset.Builder()
186                         .setField(ID_USERNAME, "aa")
187                         .setPresentation(createPresentation(aa))
188                         .build())
189                 .addDataset(new CannedDataset.Builder()
190                         .setField(ID_USERNAME, "ab")
191                         .setPresentation(createPresentation(ab))
192                         .build())
193                 .addDataset(new CannedDataset.Builder()
194                         .setField(ID_USERNAME, "b")
195                         .setPresentation(createPresentation(b))
196                         .build())
197                 .build());
198 
199         final ImeEventStream stream = mockImeSession.openEventStream();
200 
201         // Trigger auto-fill.
202         mActivity.onUsername(View::requestFocus);
203 
204         // Wait until the MockIme gets bound to the TestActivity.
205         expectBindInput(stream, Process.myPid(), MOCK_IME_TIMEOUT_MS);
206         expectEvent(stream, editorMatcher("onStartInput", mActivity.getUsername().getId()),
207                 MOCK_IME_TIMEOUT_MS);
208 
209         sReplier.getNextFillRequest();
210 
211         // With no filter text all datasets should be shown
212         mUiBot.assertDatasets(aa, ab, b);
213 
214         // Only two datasets start with 'a'
215         final ImeCommand cmd1 = mockImeSession.callCommitText("a", 1);
216         expectCommand(stream, cmd1, MOCK_IME_TIMEOUT_MS);
217         mUiBot.assertDatasets(aa, ab);
218 
219         // Only one dataset start with 'aa'
220         final ImeCommand cmd2 = mockImeSession.callCommitText("a", 1);
221         expectCommand(stream, cmd2, MOCK_IME_TIMEOUT_MS);
222         mUiBot.assertDatasets(aa);
223 
224         // Only two datasets start with 'a'
225         final ImeCommand cmd3 = mockImeSession.callSendDownUpKeyEvents(KeyEvent.KEYCODE_DEL);
226         expectCommand(stream, cmd3, MOCK_IME_TIMEOUT_MS);
227         mUiBot.assertDatasets(aa, ab);
228 
229         // With no filter text all datasets should be shown
230         final ImeCommand cmd4 = mockImeSession.callSendDownUpKeyEvents(KeyEvent.KEYCODE_DEL);
231         expectCommand(stream, cmd4, MOCK_IME_TIMEOUT_MS);
232         mUiBot.assertDatasets(aa, ab, b);
233 
234         // No dataset start with 'aaa'
235         final MyAutofillCallback callback = mActivity.registerCallback();
236         final ImeCommand cmd5 = mockImeSession.callCommitText("aaa", 1);
237         expectCommand(stream, cmd5, MOCK_IME_TIMEOUT_MS);
238         callback.assertUiHiddenEvent(mActivity.getUsername());
239         mUiBot.assertNoDatasets();
240     }
241 
242     @Test
243     @AppModeFull(reason = "testFilter() is enough")
testFilter_nullValuesAlwaysMatched()244     public void testFilter_nullValuesAlwaysMatched() throws Exception {
245         final String aa = "Two A's";
246         final String ab = "A and B";
247         final String b = "Only B";
248 
249         enableService();
250 
251         // Set expectations.
252         sReplier.addResponse(new CannedFillResponse.Builder()
253                 .addDataset(new CannedDataset.Builder()
254                         .setField(ID_USERNAME, "aa")
255                         .setPresentation(createPresentation(aa))
256                         .build())
257                 .addDataset(new CannedDataset.Builder()
258                         .setField(ID_USERNAME, "ab")
259                         .setPresentation(createPresentation(ab))
260                         .build())
261                 .addDataset(new CannedDataset.Builder()
262                         .setField(ID_USERNAME, (String) null)
263                         .setPresentation(createPresentation(b))
264                         .build())
265                 .build());
266 
267         // Trigger auto-fill.
268         requestFocusOnUsername();
269         sReplier.getNextFillRequest();
270 
271         // With no filter text all datasets should be shown
272         mUiBot.assertDatasets(aa, ab, b);
273 
274         // Two datasets start with 'a' and one with null value always shown
275         changeUsername("a");
276         mUiBot.assertDatasets(aa, ab, b);
277 
278         // One dataset start with 'aa' and one with null value always shown
279         changeUsername("aa");
280         mUiBot.assertDatasets(aa, b);
281 
282         // Two datasets start with 'a' and one with null value always shown
283         changeUsername("a");
284         mUiBot.assertDatasets(aa, ab, b);
285 
286         // With no filter text all datasets should be shown
287         changeUsername("");
288         mUiBot.assertDatasets(aa, ab, b);
289 
290         // No dataset start with 'aaa' and one with null value always shown
291         changeUsername("aaa");
292         mUiBot.assertDatasets(b);
293     }
294 
295     @Test
296     @AppModeFull(reason = "testFilter() is enough")
testFilter_differentPrefixes()297     public void testFilter_differentPrefixes() throws Exception {
298         final String a = "aaa";
299         final String b = "bra";
300         final String c = "cadabra";
301 
302         enableService();
303 
304         // Set expectations.
305         sReplier.addResponse(new CannedFillResponse.Builder()
306                 .addDataset(new CannedDataset.Builder()
307                         .setField(ID_USERNAME, a)
308                         .setPresentation(createPresentation(a))
309                         .build())
310                 .addDataset(new CannedDataset.Builder()
311                         .setField(ID_USERNAME, b)
312                         .setPresentation(createPresentation(b))
313                         .build())
314                 .addDataset(new CannedDataset.Builder()
315                         .setField(ID_USERNAME, c)
316                         .setPresentation(createPresentation(c))
317                         .build())
318                 .build());
319 
320         // Trigger auto-fill.
321         requestFocusOnUsername();
322         sReplier.getNextFillRequest();
323 
324         // With no filter text all datasets should be shown
325         mUiBot.assertDatasets(a, b, c);
326 
327         changeUsername("a");
328         mUiBot.assertDatasets(a);
329 
330         changeUsername("b");
331         mUiBot.assertDatasets(b);
332 
333         changeUsername("c");
334         mUiBot.assertDatasets(c);
335     }
336 
337     @Test
338     @AppModeFull(reason = "testFilter() is enough")
testFilter_usingRegex()339     public void testFilter_usingRegex() throws Exception {
340         // Dataset presentations.
341         final String aa = "Two A's";
342         final String ab = "A and B";
343         final String b = "Only B";
344 
345         enableService();
346 
347         // Set expectations.
348         sReplier.addResponse(new CannedFillResponse.Builder()
349                 .addDataset(new CannedDataset.Builder()
350                         .setField(ID_USERNAME, "whatever", Pattern.compile("a|aa"))
351                         .setPresentation(createPresentation(aa))
352                         .build())
353                 .addDataset(new CannedDataset.Builder()
354                         .setField(ID_USERNAME, "whatsoever", createPresentation(ab),
355                                 Pattern.compile("a|ab"))
356                         .build())
357                 .addDataset(new CannedDataset.Builder()
358                         .setField(ID_USERNAME, (String) null, Pattern.compile("b"))
359                         .setPresentation(createPresentation(b))
360                         .build())
361                 .build());
362 
363         // Trigger auto-fill.
364         requestFocusOnUsername();
365         sReplier.getNextFillRequest();
366 
367         // With no filter text all datasets should be shown
368         mUiBot.assertDatasets(aa, ab, b);
369 
370         // Only two datasets start with 'a'
371         changeUsername("a");
372         mUiBot.assertDatasets(aa, ab);
373 
374         // Only one dataset start with 'aa'
375         changeUsername("aa");
376         mUiBot.assertDatasets(aa);
377 
378         // Only two datasets start with 'a'
379         changeUsername("a");
380         mUiBot.assertDatasets(aa, ab);
381 
382         // With no filter text all datasets should be shown
383         changeUsername("");
384         mUiBot.assertDatasets(aa, ab, b);
385 
386         // No dataset start with 'aaa'
387         final MyAutofillCallback callback = mActivity.registerCallback();
388         changeUsername("aaa");
389         callback.assertUiHiddenEvent(mActivity.getUsername());
390         mUiBot.assertNoDatasets();
391     }
392 
393     @Test
394     @AppModeFull(reason = "testFilter() is enough")
testFilter_disabledUsingNullRegex()395     public void testFilter_disabledUsingNullRegex() throws Exception {
396         // Dataset presentations.
397         final String unfilterable = "Unfilterabled";
398         final String aOrW = "A or W";
399         final String w = "Wazzup";
400 
401         enableService();
402 
403         // Set expectations.
404         sReplier.addResponse(new CannedFillResponse.Builder()
405                 // This dataset has a value but filter is disabled
406                 .addDataset(new CannedDataset.Builder()
407                         .setUnfilterableField(ID_USERNAME, "a am I")
408                         .setPresentation(createPresentation(unfilterable))
409                         .build())
410                 // This dataset uses pattern to filter
411                 .addDataset(new CannedDataset.Builder()
412                         .setField(ID_USERNAME, "whatsoever", createPresentation(aOrW),
413                                 Pattern.compile("a|aw"))
414                         .build())
415                 // This dataset uses value to filter
416                 .addDataset(new CannedDataset.Builder()
417                         .setField(ID_USERNAME, "wazzup")
418                         .setPresentation(createPresentation(w))
419                         .build())
420                 .build());
421 
422         // Trigger auto-fill.
423         requestFocusOnUsername();
424         sReplier.getNextFillRequest();
425 
426         // With no filter text all datasets should be shown
427         mUiBot.assertDatasets(unfilterable, aOrW, w);
428 
429         // Only one dataset start with 'a'
430         changeUsername("a");
431         mUiBot.assertDatasets(aOrW);
432 
433         // No dataset starts with 'aa'
434         changeUsername("aa");
435         mUiBot.assertNoDatasets();
436 
437         // Only one datasets start with 'a'
438         changeUsername("a");
439         mUiBot.assertDatasets(aOrW);
440 
441         // With no filter text all datasets should be shown
442         changeUsername("");
443         mUiBot.assertDatasets(unfilterable, aOrW, w);
444 
445         // Only one datasets start with 'w'
446         changeUsername("w");
447         mUiBot.assertDatasets(w);
448 
449         // No dataset start with 'aaa'
450         final MyAutofillCallback callback = mActivity.registerCallback();
451         changeUsername("aaa");
452         callback.assertUiHiddenEvent(mActivity.getUsername());
453         mUiBot.assertNoDatasets();
454     }
455 
456     @Test
457     @AppModeFull(reason = "testFilter() is enough")
testFilter_mixPlainAndRegex()458     public void testFilter_mixPlainAndRegex() throws Exception {
459         final String plain = "Plain";
460         final String regexPlain = "RegexPlain";
461         final String authRegex = "AuthRegex";
462         final String kitchnSync = "KitchenSync";
463         final Pattern everything = Pattern.compile(".*");
464 
465         enableService();
466 
467         // Set expectations.
468         final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1,
469                 new CannedDataset.Builder()
470                         .setField(ID_USERNAME, "dude")
471                         .build());
472         sReplier.addResponse(new CannedFillResponse.Builder()
473                 .addDataset(new CannedDataset.Builder()
474                         .setField(ID_USERNAME, "aword")
475                         .setPresentation(createPresentation(plain))
476                         .build())
477                 .addDataset(new CannedDataset.Builder()
478                         .setField(ID_USERNAME, "a ignore", everything)
479                         .setPresentation(createPresentation(regexPlain))
480                         .build())
481                 .addDataset(new CannedDataset.Builder()
482                         .setField(ID_USERNAME, "ab ignore", everything)
483                         .setAuthentication(authentication)
484                         .setPresentation(createPresentation(authRegex))
485                         .build())
486                 .addDataset(new CannedDataset.Builder()
487                         .setField(ID_USERNAME, "ab ignore", createPresentation(kitchnSync),
488                                 everything)
489                         .build())
490                 .build());
491 
492         // Trigger auto-fill.
493         requestFocusOnUsername();
494         sReplier.getNextFillRequest();
495 
496         // With no filter text all datasets should be shown
497         mUiBot.assertDatasets(plain, regexPlain, authRegex, kitchnSync);
498 
499         // All datasets start with 'a'
500         changeUsername("a");
501         mUiBot.assertDatasets(plain, regexPlain, authRegex, kitchnSync);
502 
503         // Only the regex datasets should start with 'ab'
504         changeUsername("ab");
505         mUiBot.assertDatasets(regexPlain, authRegex, kitchnSync);
506     }
507 
508     @Test
509     @AppModeFull(reason = "testFilter_usingKeyboard() is enough")
testFilter_mixPlainAndRegex_usingKeyboard()510     public void testFilter_mixPlainAndRegex_usingKeyboard() throws Exception {
511         final String plain = "Plain";
512         final String regexPlain = "RegexPlain";
513         final String authRegex = "AuthRegex";
514         final String kitchnSync = "KitchenSync";
515         final Pattern everything = Pattern.compile(".*");
516 
517         enableService();
518 
519         // Set expectations.
520         final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1,
521                 new CannedDataset.Builder()
522                         .setField(ID_USERNAME, "dude")
523                         .build());
524         sReplier.addResponse(new CannedFillResponse.Builder()
525                 .addDataset(new CannedDataset.Builder()
526                         .setField(ID_USERNAME, "aword")
527                         .setPresentation(createPresentation(plain))
528                         .build())
529                 .addDataset(new CannedDataset.Builder()
530                         .setField(ID_USERNAME, "a ignore", everything)
531                         .setPresentation(createPresentation(regexPlain))
532                         .build())
533                 .addDataset(new CannedDataset.Builder()
534                         .setField(ID_USERNAME, "ab ignore", everything)
535                         .setAuthentication(authentication)
536                         .setPresentation(createPresentation(authRegex))
537                         .build())
538                 .addDataset(new CannedDataset.Builder()
539                         .setField(ID_USERNAME, "ab ignore", createPresentation(kitchnSync),
540                                 everything)
541                         .build())
542                 .build());
543 
544         // Trigger auto-fill.
545         requestFocusOnUsername();
546         sReplier.getNextFillRequest();
547 
548         // With no filter text all datasets should be shown
549         mUiBot.assertDatasets(plain, regexPlain, authRegex, kitchnSync);
550 
551         // All datasets start with 'a'
552         sendKeyEvent("KEYCODE_A");
553         mUiBot.assertDatasets(plain, regexPlain, authRegex, kitchnSync);
554 
555         // Only the regex datasets should start with 'ab'
556         sendKeyEvent("KEYCODE_B");
557         mUiBot.assertDatasets(regexPlain, authRegex, kitchnSync);
558     }
559 
560     @Test
561     @AppModeFull(reason = "testFilter() is enough")
testFilter_resetFilter_chooseFirst()562     public void testFilter_resetFilter_chooseFirst() throws Exception {
563         resetFilterTest(1);
564     }
565 
566     @Test
567     @AppModeFull(reason = "testFilter() is enough")
testFilter_resetFilter_chooseSecond()568     public void testFilter_resetFilter_chooseSecond() throws Exception {
569         resetFilterTest(2);
570     }
571 
572     @Test
573     @AppModeFull(reason = "testFilter() is enough")
testFilter_resetFilter_chooseThird()574     public void testFilter_resetFilter_chooseThird() throws Exception {
575         resetFilterTest(3);
576     }
577 
resetFilterTest(int number)578     private void resetFilterTest(int number) throws Exception {
579         final String aa = "Two A's";
580         final String ab = "A and B";
581         final String b = "Only B";
582 
583         enableService();
584 
585         // Set expectations.
586         sReplier.addResponse(new CannedFillResponse.Builder()
587                 .addDataset(new CannedDataset.Builder()
588                         .setField(ID_USERNAME, "aa")
589                         .setPresentation(createPresentation(aa))
590                         .build())
591                 .addDataset(new CannedDataset.Builder()
592                         .setField(ID_USERNAME, "ab")
593                         .setPresentation(createPresentation(ab))
594                         .build())
595                 .addDataset(new CannedDataset.Builder()
596                         .setField(ID_USERNAME, "b")
597                         .setPresentation(createPresentation(b))
598                         .build())
599                 .build());
600 
601         final String chosenOne;
602         switch (number) {
603             case 1:
604                 chosenOne = aa;
605                 mActivity.expectAutoFill("aa");
606                 break;
607             case 2:
608                 chosenOne = ab;
609                 mActivity.expectAutoFill("ab");
610                 break;
611             case 3:
612                 chosenOne = b;
613                 mActivity.expectAutoFill("b");
614                 break;
615             default:
616                 throw new IllegalArgumentException("invalid dataset number: " + number);
617         }
618 
619         final MyAutofillCallback callback = mActivity.registerCallback();
620         final EditText username = mActivity.getUsername();
621 
622         // Trigger auto-fill.
623         requestFocusOnUsername();
624         callback.assertUiShownEvent(username);
625 
626         sReplier.getNextFillRequest();
627 
628         // With no filter text all datasets should be shown
629         mUiBot.assertDatasets(aa, ab, b);
630 
631         // Only two datasets start with 'a'
632         changeUsername("a");
633         mUiBot.assertDatasets(aa, ab);
634 
635         // One dataset starts with 'aa'
636         changeUsername("aa");
637         mUiBot.assertDatasets(aa);
638 
639         // Filter all out
640         changeUsername("aaa");
641         callback.assertUiHiddenEvent(username);
642         mUiBot.assertNoDatasets();
643 
644         // Now delete the char and assert aa is showing again
645         changeUsername("aa");
646         callback.assertUiShownEvent(username);
647         mUiBot.assertDatasets(aa);
648 
649         // Delete one more and assert two datasets showing
650         changeUsername("a");
651         mUiBot.assertDatasets(aa, ab);
652 
653         // Reset back to all choices
654         changeUsername("");
655         mUiBot.assertDatasets(aa, ab, b);
656 
657         // select the choice
658         mUiBot.selectDataset(chosenOne);
659         callback.assertUiHiddenEvent(username);
660         mUiBot.assertNoDatasets();
661 
662         // Check the results.
663         mActivity.assertAutoFilled();
664     }
665 }
666