1 /*
2  * Copyright (C) 2023 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.servicebehavior;
17 
18 import static android.app.Activity.RESULT_CANCELED;
19 import static android.app.Activity.RESULT_OK;
20 import static android.autofillservice.cts.testcore.Helper.ID_PASSWORD;
21 import static android.autofillservice.cts.testcore.Helper.ID_USERNAME;
22 import static android.autofillservice.cts.testcore.Helper.UNUSED_AUTOFILL_VALUE;
23 import static android.autofillservice.cts.testcore.Helper.assertHasFlags;
24 import static android.autofillservice.cts.testcore.Helper.disableFillDialogFeature;
25 import static android.autofillservice.cts.testcore.Helper.disablePccDetectionFeature;
26 import static android.autofillservice.cts.testcore.Helper.enableFillDialogFeature;
27 import static android.autofillservice.cts.testcore.Helper.enablePccDetectionFeature;
28 import static android.autofillservice.cts.testcore.Helper.isImeShowing;
29 import static android.autofillservice.cts.testcore.Helper.preferPccDetectionOverProvider;
30 import static android.service.autofill.FillRequest.FLAG_SUPPORTS_FILL_DIALOG;
31 
32 import static com.google.common.truth.Truth.assertThat;
33 
34 import static org.junit.Assume.assumeTrue;
35 
36 import android.autofillservice.cts.activities.AuthenticationActivity;
37 import android.autofillservice.cts.activities.LoginActivity;
38 import android.autofillservice.cts.commontests.FieldClassificationServiceManualActivityLaunchTestCase;
39 import android.autofillservice.cts.testcore.CannedFieldClassificationResponse;
40 import android.autofillservice.cts.testcore.CannedFillResponse;
41 import android.autofillservice.cts.testcore.Helper;
42 import android.autofillservice.cts.testcore.IdMode;
43 import android.autofillservice.cts.testcore.InstrumentedAutoFillService;
44 import android.autofillservice.cts.testcore.MyAutofillCallback;
45 import android.content.IntentSender;
46 import android.os.Bundle;
47 import android.platform.test.annotations.AppModeFull;
48 import android.view.View;
49 
50 import androidx.test.uiautomator.UiObject2;
51 
52 import org.junit.After;
53 import org.junit.Before;
54 import org.junit.Ignore;
55 import org.junit.Test;
56 
57 import java.util.Set;
58 import java.util.concurrent.TimeoutException;
59 
60 @AppModeFull
61 public class PccFieldClassificationTest extends
62         FieldClassificationServiceManualActivityLaunchTestCase {
63 
64     public static final String DROPDOWN_PRESENTATION = "Dropdown Presentation";
65     public static final String DROPDOWN_PRESENTATION2 = "Dropdown Presentation2";
66     public static final String DIALOG_PRESENTATION = "Dialog Presentation";
67 
68     /*
69       Ideally, autofill hints should be taken directly from HintConstants defined in androidx
70       library at https://developer.android.com/reference/androidx/autofill/HintConstants.
71 
72       There are 2 options
73 
74       First is to refactor our test infra so that we can specify which datasets are for hints,
75       which are for autofill ids and which ones are for both. We can also consider using helper
76       utilities to bring it close to actual Dataset, instead of using CannedDataset. This may
77       require some refactoring.
78 
79       Second is to use activities with their resources naming in such a way that it doesn't
80       conflict with the hints name. Since, we want to support PCC testing across existing tests
81       which use same naming for some hints (eg: username, password), this may require refactoring.
82       This approach isn't ideal since test writer needs to keep this in mind.
83 
84       For now, we just use autofill hints name with "hint_" prefix, but actual services shouldn't
85        do this.
86      */
87     public static final String AUTOFILL_HINT_PASSWORD = "hint_password";
88     public static final String AUTOFILL_HINT_USERNAME = "hint_username";
89     public static final String AUTOFILL_HINT_NEW_PASSWORD = "hint_new_password";
90 
91     @Before
setup()92     public void setup() throws Exception {
93         assumeTrue("PCC is enabled", Helper.isPccSupported(mContext));
94 
95         enableService();
96         enablePccDetectionFeature(sContext, AUTOFILL_HINT_USERNAME, AUTOFILL_HINT_PASSWORD,
97                 AUTOFILL_HINT_NEW_PASSWORD);
98         enablePccDetectionService();
99         sReplier.setIdMode(IdMode.PCC_ID);
100     }
101 
102     @After
destroy()103     public void destroy() {
104         sReplier.setIdMode(IdMode.RESOURCE_ID);
105         disablePccDetectionFeature(sContext);
106     }
107 
108     @Test
109     @Ignore("PCC turndown")
testFieldClassificationRequestIsSentWhenScreenEntered()110     public void testFieldClassificationRequestIsSentWhenScreenEntered() throws Exception {
111 
112         sClassificationReplier.addResponse(new CannedFieldClassificationResponse.Builder()
113                 .addFieldClassification(
114                         new CannedFieldClassificationResponse.CannedFieldClassification(
115                                 ID_PASSWORD, Set.of(AUTOFILL_HINT_PASSWORD)))
116                 .build());
117 
118         startLoginActivity();
119         mUiBot.waitForIdleSync();
120         sClassificationReplier.getNextFieldClassificationRequest();
121         sClassificationReplier.assertNoUnhandledFieldClassificationRequests();
122     }
123 
124     @Test
125     @Ignore("PCC turndown")
testFieldClassification_withFillDialog()126     public void testFieldClassification_withFillDialog() throws Exception {
127 
128         // Enable feature and test service
129         enableFillDialogFeature(sContext);
130         enableService();
131 
132         sClassificationReplier.addResponse(new CannedFieldClassificationResponse.Builder()
133                 .addFieldClassification(
134                         new CannedFieldClassificationResponse.CannedFieldClassification(
135                                 ID_PASSWORD, Set.of(AUTOFILL_HINT_PASSWORD)))
136                 .build());
137 
138         // Set response with a dataset > fill dialog should have two buttons
139         sReplier.addResponse(new CannedFillResponse.Builder()
140                 .addDataset(new CannedFillResponse.CannedDataset.Builder()
141                         .setField(ID_USERNAME, "dude")
142                         .setField(ID_PASSWORD, "sweet")
143                         .setPresentation(createPresentation(DROPDOWN_PRESENTATION))
144                         .setDialogPresentation(createPresentation(DIALOG_PRESENTATION))
145                         .build())
146                 .setDialogHeader(createPresentation("Dialog Header"))
147                 .setDialogTriggerIds(ID_PASSWORD)
148                 .build());
149 
150         // Start activity and autofill
151         LoginActivity activity = startLoginActivity();
152         mUiBot.waitForIdleSync();
153 
154 
155         // Assert FieldClassification request was sent
156         sClassificationReplier.getNextFieldClassificationRequest();
157         sClassificationReplier.assertNoUnhandledFieldClassificationRequests();
158 
159         // Check onFillRequest has the flag: FLAG_SUPPORTS_FILL_DIALOG
160         final InstrumentedAutoFillService.FillRequest fillRequest =
161                 sReplier.getNextFillRequest();
162         assertHasFlags(fillRequest.flags, FLAG_SUPPORTS_FILL_DIALOG);
163         // assert that request contains hints
164         assertFillRequestHints(fillRequest);
165         sReplier.assertNoUnhandledFillRequests();
166         mUiBot.waitForIdleSync();
167 
168         // Click on password field to trigger fill dialog
169         mUiBot.selectByRelativeId(ID_PASSWORD);
170         mUiBot.waitForIdleSync();
171 
172         // Verify IME is not shown
173         assertThat(isImeShowing(activity.getRootWindowInsets())).isFalse();
174 
175         // Verify the content of fill dialog, and then select dataset in fill dialog
176         mUiBot.assertFillDialogHeader("Dialog Header");
177         mUiBot.assertFillDialogRejectButton();
178         mUiBot.assertFillDialogAcceptButton();
179         final UiObject2 picker = mUiBot.assertFillDialogDatasets(DIALOG_PRESENTATION);
180 
181         // Set expected value, then select dataset
182         activity.expectAutoFill("dude", "sweet");
183         mUiBot.selectDataset(picker, DIALOG_PRESENTATION);
184 
185         // Check the results.
186         activity.assertAutoFilled();
187     }
188 
189     @Test
190     @Ignore("PCC turndown")
testFieldClassification_withDropdownDialog()191     public void testFieldClassification_withDropdownDialog() throws Exception {
192 
193         // Enable feature and test service
194         disableFillDialogFeature(sContext);
195         enableService();
196 
197         sClassificationReplier.addResponse(new CannedFieldClassificationResponse.Builder()
198                 .addFieldClassification(
199                         new CannedFieldClassificationResponse.CannedFieldClassification(
200                                 ID_PASSWORD, Set.of(AUTOFILL_HINT_PASSWORD)))
201                 .build());
202 
203         // Set response with a dataset > fill dialog should have two buttons
204         sReplier.addResponse(new CannedFillResponse.Builder()
205                 .addDataset(new CannedFillResponse.CannedDataset.Builder()
206                         .setField(ID_USERNAME, "dude")
207                         .setField(ID_PASSWORD, "sweet")
208                         .setPresentation(createPresentation(DROPDOWN_PRESENTATION))
209                         .build())
210                 .build());
211 
212         // Start activity and autofill
213         LoginActivity activity = startLoginActivity();
214         mUiBot.waitForIdleSync();
215 
216         // Set expected value
217         activity.expectAutoFill("dude", "sweet");
218 
219         // Assert FieldClassification request was sent
220         sClassificationReplier.getNextFieldClassificationRequest();
221 
222         // Click on password field to trigger fill dialog
223         mUiBot.selectByRelativeId(ID_PASSWORD);
224         mUiBot.waitForIdleSync();
225 
226         final InstrumentedAutoFillService.FillRequest fillRequest =
227                 sReplier.getNextFillRequest();
228         // assert that request contains hints
229         assertFillRequestHints(fillRequest);
230         mUiBot.waitForIdleSync();
231 
232 
233         // Auto-fill it.
234         final UiObject2 picker = mUiBot.assertDatasetsWithBorders(
235                 null /* expectedHeader */, null /* expectedFooter */, DROPDOWN_PRESENTATION);
236         mUiBot.selectDataset(picker, DROPDOWN_PRESENTATION);
237 
238         // Check the results.
239         activity.assertAutoFilled();
240 
241         sClassificationReplier.assertNoUnhandledFieldClassificationRequests();
242         sReplier.assertNoUnhandledFillRequests();
243     }
244 
245     @Test
246     @Ignore("PCC turndown")
testFieldClassification_mergeResultsFromPccAndProvider_sameDataset()247     public void testFieldClassification_mergeResultsFromPccAndProvider_sameDataset()
248             throws Exception {
249 
250         // Enable feature and test service
251         disableFillDialogFeature(sContext);
252         enableService();
253 
254         sClassificationReplier.addResponse(new CannedFieldClassificationResponse.Builder()
255                 .addFieldClassification(
256                         new CannedFieldClassificationResponse.CannedFieldClassification(
257                                 ID_PASSWORD, Set.of(AUTOFILL_HINT_PASSWORD)))
258                 .build());
259 
260         // Set response with a dataset > fill dialog should have two buttons
261         sReplier.addResponse(new CannedFillResponse.Builder()
262                 .addDataset(new CannedFillResponse.CannedDataset.Builder()
263                         .setField(ID_USERNAME, "dude")
264                         .setField(AUTOFILL_HINT_PASSWORD, "sweet")
265                         .setPresentation(createPresentation(DROPDOWN_PRESENTATION))
266                         .build())
267                 .build());
268 
269         // Start activity and autofill
270         LoginActivity activity = startLoginActivity();
271         mUiBot.waitForIdleSync();
272 
273         // Assert FieldClassification request was sent
274         sClassificationReplier.getNextFieldClassificationRequest();
275 
276         // Set expected value
277         // TODO: change it back to assert both username and password in next release
278         activity.expectPasswordAutoFill("sweet");
279 
280         // Click on password field to trigger autofill
281         mUiBot.selectByRelativeId(ID_PASSWORD);
282         mUiBot.waitForIdleSync();
283 
284         final InstrumentedAutoFillService.FillRequest fillRequest =
285                 sReplier.getNextFillRequest();
286         // assert that request contains hints
287         assertFillRequestHints(fillRequest);
288         mUiBot.waitForIdleSync();
289 
290 
291         // Auto-fill it.
292         UiObject2 picker = mUiBot.assertDatasetsWithBorders(
293                 null /* expectedHeader */, null /* expectedFooter */, DROPDOWN_PRESENTATION);
294         mUiBot.selectDataset(picker, DROPDOWN_PRESENTATION);
295 
296         // Check the results.
297         activity.assertAutoFilled();
298 
299 
300         // Set expected value
301         activity.expectAutoFill("dude");
302 
303         sClassificationReplier.assertNoUnhandledFieldClassificationRequests();
304         sReplier.assertNoUnhandledFillRequests();
305     }
306 
307     @Test
308     @Ignore("PCC turndown")
testFieldClassification_mergeResultsFromPccAndProvider_separateDataset()309     public void testFieldClassification_mergeResultsFromPccAndProvider_separateDataset()
310             throws Exception {
311 
312         // Enable feature and test service
313         disableFillDialogFeature(sContext);
314         enableService();
315 
316         sClassificationReplier.addResponse(new CannedFieldClassificationResponse.Builder()
317                 .addFieldClassification(
318                         new CannedFieldClassificationResponse.CannedFieldClassification(
319                                 ID_USERNAME, Set.of(AUTOFILL_HINT_USERNAME)))
320                 .addFieldClassification(
321                         new CannedFieldClassificationResponse.CannedFieldClassification(
322                                 ID_PASSWORD, Set.of(AUTOFILL_HINT_PASSWORD)))
323                 .build());
324 
325         // Set response with a dataset > fill dialog should have two buttons
326         sReplier.addResponse(new CannedFillResponse.Builder()
327                 .addDataset(new CannedFillResponse.CannedDataset.Builder()
328                         .setField(ID_USERNAME, "dude")
329                         .setPresentation(createPresentation(DROPDOWN_PRESENTATION))
330                         .build())
331                 .addDataset(new CannedFillResponse.CannedDataset.Builder()
332                         .setField(AUTOFILL_HINT_PASSWORD, "sweet")
333                         .setPresentation(createPresentation(DROPDOWN_PRESENTATION2))
334                         .build())
335                 .build());
336 
337         // Start activity and autofill
338         LoginActivity activity = startLoginActivity();
339         mUiBot.waitForIdleSync();
340 
341         // Assert FieldClassification request was sent
342         sClassificationReplier.getNextFieldClassificationRequest();
343 
344         // Set expected value for password only
345         activity.expectPasswordAutoFill("sweet");
346 
347         // Click on password field to trigger autofill
348         mUiBot.selectByRelativeId(ID_PASSWORD);
349         mUiBot.waitForIdleSync();
350 
351         final InstrumentedAutoFillService.FillRequest fillRequest =
352                 sReplier.getNextFillRequest();
353         // assert that request contains hints
354         assertFillRequestHints(fillRequest);
355         mUiBot.waitForIdleSync();
356 
357 
358         // Auto-fill it.
359         UiObject2 picker = mUiBot.assertDatasetsWithBorders(
360                 null /* expectedHeader */, null /* expectedFooter */, DROPDOWN_PRESENTATION2);
361         mUiBot.selectDataset(picker, DROPDOWN_PRESENTATION2);
362 
363         // Check the results.
364         activity.assertAutoFilled();
365 
366 
367         // Set expected value for username
368         activity.expectAutoFill("dude");
369 
370         // Click on username field to see presentation from previous autofill request.
371         mUiBot.selectByRelativeId(ID_USERNAME);
372         mUiBot.waitForIdleSync();
373 
374 
375         // Auto-fill it.
376         picker = mUiBot.assertDatasetsWithBorders(
377                 null /* expectedHeader */, null /* expectedFooter */, DROPDOWN_PRESENTATION);
378         mUiBot.selectDataset(picker, DROPDOWN_PRESENTATION);
379 
380         // Check the results.
381         activity.assertAutoFilled();
382 
383         sClassificationReplier.assertNoUnhandledFieldClassificationRequests();
384         sReplier.assertNoUnhandledFillRequests();
385     }
386 
387     @Test
388     @Ignore("PCC turndown")
testFieldClassification_preferPccDetection_sameDetection()389     public void testFieldClassification_preferPccDetection_sameDetection() throws Exception {
390 
391         // Enable feature and test service
392         disableFillDialogFeature(sContext);
393         preferPccDetectionOverProvider(sContext, true);
394         enableService();
395 
396         sClassificationReplier.addResponse(new CannedFieldClassificationResponse.Builder()
397                 .addFieldClassification(
398                         new CannedFieldClassificationResponse.CannedFieldClassification(
399                                 ID_USERNAME, Set.of(AUTOFILL_HINT_USERNAME)))
400                 .addFieldClassification(
401                         new CannedFieldClassificationResponse.CannedFieldClassification(
402                                 ID_PASSWORD, Set.of(AUTOFILL_HINT_PASSWORD)))
403                 .build());
404 
405         // Set response with a dataset > fill dialog should have two buttons
406         sReplier.addResponse(new CannedFillResponse.Builder()
407                 .addDataset(new CannedFillResponse.CannedDataset.Builder()
408                         .setField(ID_USERNAME, "username")
409                         .setField(ID_PASSWORD, "password")
410                         .setPresentation(createPresentation(DROPDOWN_PRESENTATION))
411                         .build())
412                 .addDataset(new CannedFillResponse.CannedDataset.Builder()
413                         .setField(AUTOFILL_HINT_USERNAME, "hint_username")
414                         .setField(AUTOFILL_HINT_PASSWORD, "hint_password")
415                         .setPresentation(createPresentation(DROPDOWN_PRESENTATION2))
416                         .build())
417                 .build());
418 
419         // Start activity and autofill
420         LoginActivity activity = startLoginActivity();
421         mUiBot.waitForIdleSync();
422 
423         // Assert FieldClassification request was sent
424         sClassificationReplier.getNextFieldClassificationRequest();
425 
426         // Set expected value
427         activity.expectAutoFill("hint_username", "hint_password");
428 
429         // Click on password field to trigger autofill
430         mUiBot.selectByRelativeId(ID_PASSWORD);
431         mUiBot.waitForIdleSync();
432 
433         final InstrumentedAutoFillService.FillRequest fillRequest =
434                 sReplier.getNextFillRequest();
435         // assert that request contains hints
436         assertFillRequestHints(fillRequest);
437         mUiBot.waitForIdleSync();
438 
439         // Auto-fill it.
440         final UiObject2 picker = mUiBot.assertDatasetsWithBorders(
441                 null /* expectedHeader */, null /* expectedFooter */, DROPDOWN_PRESENTATION2);
442         mUiBot.selectDataset(picker, DROPDOWN_PRESENTATION2);
443 
444         // Check the results.
445         activity.assertAutoFilled();
446 
447         sClassificationReplier.assertNoUnhandledFieldClassificationRequests();
448         sReplier.assertNoUnhandledFillRequests();
449 
450         sClassificationReplier.assertNoUnhandledFieldClassificationRequests();
451         sReplier.assertNoUnhandledFillRequests();
452     }
453 
454     @Test
455     @Ignore("PCC turndown")
testFieldClassification_noDetectionFromProvider()456     public void testFieldClassification_noDetectionFromProvider() throws Exception {
457         preferPccDetectionOverProvider(sContext, false);
458         testNoDetectionFromProvider();
459     }
460 
461     @Test
462     @Ignore("PCC turndown")
testFieldClassification_noDetectionFromProvider_preferPcc()463     public void testFieldClassification_noDetectionFromProvider_preferPcc() throws Exception {
464         preferPccDetectionOverProvider(sContext, true);
465         testNoDetectionFromProvider();
466     }
467 
testNoDetectionFromProvider()468     private void testNoDetectionFromProvider() throws Exception {
469 
470         // Enable feature and test service
471         disableFillDialogFeature(sContext);
472         enableService();
473 
474         sClassificationReplier.addResponse(new CannedFieldClassificationResponse.Builder()
475                 .addFieldClassification(
476                         new CannedFieldClassificationResponse.CannedFieldClassification(
477                                 ID_USERNAME, Set.of(AUTOFILL_HINT_USERNAME)))
478                 .addFieldClassification(
479                         new CannedFieldClassificationResponse.CannedFieldClassification(
480                                 ID_PASSWORD, Set.of(AUTOFILL_HINT_PASSWORD)))
481                 .build());
482 
483         // Set response with a dataset > fill dialog should have two buttons
484         sReplier.addResponse(new CannedFillResponse.Builder()
485                 .addDataset(new CannedFillResponse.CannedDataset.Builder()
486                         .setField(AUTOFILL_HINT_USERNAME, "hint_username")
487                         .setField(AUTOFILL_HINT_PASSWORD, "hint_password")
488                         .setPresentation(createPresentation(DROPDOWN_PRESENTATION))
489                         .build())
490                 .build());
491 
492         // Start activity and autofill
493         LoginActivity activity = startLoginActivity();
494         mUiBot.waitForIdleSync();
495 
496         // Assert FieldClassification request was sent
497         sClassificationReplier.getNextFieldClassificationRequest();
498 
499         // Set expected value
500         activity.expectAutoFill("hint_username", "hint_password");
501 
502         // Click on password field to trigger autofill
503         mUiBot.selectByRelativeId(ID_PASSWORD);
504         mUiBot.waitForIdleSync();
505 
506         final InstrumentedAutoFillService.FillRequest fillRequest =
507                 sReplier.getNextFillRequest();
508         // assert that request contains hints
509         assertFillRequestHints(fillRequest);
510         mUiBot.waitForIdleSync();
511 
512         // Auto-fill it.
513         final UiObject2 picker = mUiBot.assertDatasetsWithBorders(
514                 null /* expectedHeader */, null /* expectedFooter */, DROPDOWN_PRESENTATION);
515         mUiBot.selectDataset(picker, DROPDOWN_PRESENTATION);
516 
517         // Check the results.
518         activity.assertAutoFilled();
519 
520         sClassificationReplier.assertNoUnhandledFieldClassificationRequests();
521         sReplier.assertNoUnhandledFillRequests();
522 
523         sClassificationReplier.assertNoUnhandledFieldClassificationRequests();
524         sReplier.assertNoUnhandledFillRequests();
525     }
526 
527     @Test
528     @Ignore("PCC turndown")
testDatasetAuthTwoFields()529     public void testDatasetAuthTwoFields() throws Exception {
530         datasetAuthTwoFields(false);
531     }
532 
533     @Test
534     @Ignore("PCC turndown")
535     @AppModeFull(reason = "testDatasetAuthTwoFields() is enough")
testDatasetAuthTwoFieldsUserCancelsFirstAttempt()536     public void testDatasetAuthTwoFieldsUserCancelsFirstAttempt() throws Exception {
537         datasetAuthTwoFields(true);
538     }
539 
datasetAuthTwoFields(boolean cancelFirstAttempt)540     private void datasetAuthTwoFields(boolean cancelFirstAttempt) throws Exception {
541         // Set service.
542         disableFillDialogFeature(sContext);
543 
544         sClassificationReplier.addResponse(new CannedFieldClassificationResponse.Builder()
545                 .addFieldClassification(
546                         new CannedFieldClassificationResponse.CannedFieldClassification(
547                                 ID_USERNAME, Set.of(AUTOFILL_HINT_USERNAME)))
548                 .addFieldClassification(
549                         new CannedFieldClassificationResponse.CannedFieldClassification(
550                                 ID_PASSWORD, Set.of(AUTOFILL_HINT_PASSWORD)))
551                 .build());
552 
553         // Prepare the authenticated response
554         final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1,
555                 new CannedFillResponse.CannedDataset.Builder()
556                         .setField(AUTOFILL_HINT_USERNAME, "dude")
557                         .setField(AUTOFILL_HINT_PASSWORD, "sweet")
558                         .build());
559 
560         // Configure the service behavior
561         sReplier.addResponse(new CannedFillResponse.Builder()
562                 .addDataset(new CannedFillResponse.CannedDataset.Builder()
563                         .setField(AUTOFILL_HINT_USERNAME, UNUSED_AUTOFILL_VALUE)
564                         .setField(AUTOFILL_HINT_PASSWORD, UNUSED_AUTOFILL_VALUE)
565                         .setPresentation(createPresentation("Tap to auth dataset"))
566                         .setAuthentication(authentication)
567                         .build())
568                 .build());
569 
570         // Start activity and autofill
571         LoginActivity activity = startLoginActivity();
572         final MyAutofillCallback callback = activity.registerCallback();
573         mUiBot.waitForIdleSync();
574 
575         // Assert FieldClassification request was sent
576         sClassificationReplier.getNextFieldClassificationRequest();
577 
578         // Set expectation for the activity
579         activity.expectAutoFill("dude", "sweet");
580 
581         // Trigger auto-fill.
582 
583         // Click on password field to trigger autofill
584         requestFocusOnUsername(activity);
585 
586         // Wait for onFill() before proceeding.
587         sReplier.getNextFillRequest();
588         final View username = activity.getUsername();
589         callback.assertUiShownEvent(username);
590         mUiBot.assertDatasets("Tap to auth dataset");
591 
592         // Make sure UI is show on 2nd field as well
593         final View password = activity.getPassword();
594         // Click on password field to trigger autofill
595         requestFocusOnPassword(activity);
596 
597         callback.assertUiHiddenEvent(username);
598         callback.assertUiShownEvent(password);
599         mUiBot.assertDatasets("Tap to auth dataset");
600 
601         // Now tap on 1st field to show it again...
602         requestFocusOnUsername(activity);
603         callback.assertUiHiddenEvent(password);
604         callback.assertUiShownEvent(username);
605         mUiBot.assertDatasets("Tap to auth dataset");
606 
607         if (cancelFirstAttempt) {
608             // Trigger the auth dialog, but emulate cancel.
609             AuthenticationActivity.setResultCode(RESULT_CANCELED);
610             mUiBot.selectDataset("Tap to auth dataset");
611             callback.assertUiHiddenEvent(username);
612             callback.assertUiShownEvent(username);
613             mUiBot.assertDatasets("Tap to auth dataset");
614 
615             // Make sure it's still shown on other fields...
616             requestFocusOnPassword(activity);
617             callback.assertUiHiddenEvent(username);
618             callback.assertUiShownEvent(password);
619             mUiBot.assertDatasets("Tap to auth dataset");
620 
621             // Tap on 1st field to show it again...
622             requestFocusOnUsername(activity);
623             callback.assertUiHiddenEvent(password);
624             callback.assertUiShownEvent(username);
625         }
626 
627         // ...and select it this time
628         AuthenticationActivity.setResultCode(RESULT_OK);
629         mUiBot.selectDataset("Tap to auth dataset");
630         callback.assertUiHiddenEvent(username);
631         mUiBot.assertNoDatasets();
632 
633         // Check the results.
634         activity.assertAutoFilled();
635     }
636 
637     @Test
638     @Ignore("PCC turndown")
testFillResponseAuthBothFields()639     public void testFillResponseAuthBothFields() throws Exception {
640         fillResponseAuthBothFields(false);
641     }
642 
643     @Test
644     @Ignore("PCC turndown")
645     @AppModeFull(reason = "testFillResponseAuthBothFields() is enough")
testFillResponseAuthBothFieldsUserCancelsFirstAttempt()646     public void testFillResponseAuthBothFieldsUserCancelsFirstAttempt() throws Exception {
647         fillResponseAuthBothFields(true);
648     }
649 
fillResponseAuthBothFields(boolean cancelFirstAttempt)650     private void fillResponseAuthBothFields(boolean cancelFirstAttempt) throws Exception {
651 
652         sClassificationReplier.addResponse(new CannedFieldClassificationResponse.Builder()
653                 .addFieldClassification(
654                         new CannedFieldClassificationResponse.CannedFieldClassification(
655                                 ID_USERNAME, Set.of(AUTOFILL_HINT_USERNAME)))
656                 .addFieldClassification(
657                         new CannedFieldClassificationResponse.CannedFieldClassification(
658                                 ID_PASSWORD, Set.of(AUTOFILL_HINT_PASSWORD)))
659                 .build());
660 
661         // Prepare the authenticated response
662         final Bundle clientState = new Bundle();
663         clientState.putString("numbers", "4815162342");
664         final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1,
665                 new CannedFillResponse.Builder().addDataset(
666                                 new CannedFillResponse.CannedDataset.Builder()
667                                         .setField(AUTOFILL_HINT_USERNAME, "dude")
668                                         .setField(AUTOFILL_HINT_PASSWORD, "sweet")
669                                         .setId("name")
670                                         .setPresentation(createPresentation("Dataset"))
671                                         .build())
672                         .setExtras(clientState).build());
673 
674         // Configure the service behavior
675         sReplier.addResponse(new CannedFillResponse.Builder()
676                 .setAuthentication(authentication, ID_USERNAME, ID_PASSWORD)
677                 .setPresentation(createPresentation("Tap to auth response"))
678                 .setExtras(clientState)
679                 .build());
680 
681         // Start activity and autofill
682         LoginActivity activity = startLoginActivity();
683         final MyAutofillCallback callback = activity.registerCallback();
684         mUiBot.waitForIdleSync();
685 
686         // Set expectation for the activity
687         activity.expectAutoFill("dude", "sweet");
688 
689         // Trigger auto-fill.
690         requestFocusOnUsername(activity);
691 
692         // Wait for onFill() before proceeding.
693         sReplier.getNextFillRequest();
694         final View username = activity.getUsername();
695         callback.assertUiShownEvent(username);
696         mUiBot.assertDatasets("Tap to auth response");
697 
698         // Make sure UI is show on 2nd field as well
699         final View password = activity.getPassword();
700         requestFocusOnPassword(activity);
701         callback.assertUiHiddenEvent(username);
702         callback.assertUiShownEvent(password);
703         mUiBot.assertDatasets("Tap to auth response");
704 
705         // Now tap on 1st field to show it again...
706 //        requestFocusOnUsername(activity); // maybe hidden by the suggestions from the first one
707 
708         requestFocusOnUsername(activity);
709         callback.assertUiHiddenEvent(password);
710         callback.assertUiShownEvent(username);
711 
712         if (cancelFirstAttempt) {
713             // Trigger the auth dialog, but emulate cancel.
714             AuthenticationActivity.setResultCode(RESULT_CANCELED);
715             mUiBot.selectDataset("Tap to auth response");
716             callback.assertUiHiddenEvent(username);
717             callback.assertUiShownEvent(username);
718             mUiBot.assertDatasets("Tap to auth response");
719 
720             // Make sure it's still shown on other fields...
721             requestFocusOnPassword(activity);
722             callback.assertUiHiddenEvent(username);
723             callback.assertUiShownEvent(password);
724             mUiBot.assertDatasets("Tap to auth response");
725 
726             // Tap on 1st field to show it again...
727             requestFocusOnUsername(activity);
728             callback.assertUiHiddenEvent(password);
729             callback.assertUiShownEvent(username);
730         }
731 
732         // ...and select it this time
733         AuthenticationActivity.setResultCode(RESULT_OK);
734         mUiBot.selectDataset("Tap to auth response");
735         callback.assertUiHiddenEvent(username);
736         callback.assertUiShownEvent(username);
737         final UiObject2 picker = mUiBot.assertDatasets("Dataset");
738         mUiBot.selectDataset(picker, "Dataset");
739         callback.assertUiHiddenEvent(username);
740         mUiBot.assertNoDatasets();
741 
742         // Check the results.
743         activity.assertAutoFilled();
744 
745         final Bundle data = AuthenticationActivity.getData();
746         assertThat(data).isNotNull();
747         final String extraValue = data.getString("numbers");
748         assertThat(extraValue).isEqualTo("4815162342");
749     }
750 
751     /**
752      * Requests focus on username and expect Window event happens.
753      */
requestFocusOnUsername(LoginActivity activity)754     protected void requestFocusOnUsername(LoginActivity activity) throws TimeoutException {
755         mUiBot.waitForWindowChange(() -> activity.onUsername(View::requestFocus));
756     }
757 
758     /**
759      * Requests focus on password and expect Window event happens.
760      */
requestFocusOnPassword(LoginActivity activity)761     protected void requestFocusOnPassword(LoginActivity activity) throws TimeoutException {
762         mUiBot.waitForWindowChange(() -> activity.onPassword(View::requestFocus));
763     }
764 
765     /**
766      * Asserts that the fill request contains hints
767      */
assertFillRequestHints(InstrumentedAutoFillService.FillRequest fillRequest)768     private void assertFillRequestHints(InstrumentedAutoFillService.FillRequest fillRequest) {
769         assertThat(fillRequest.hints.size()).isEqualTo(3);
770         assertThat(fillRequest.hints.get(0)).isEqualTo(AUTOFILL_HINT_USERNAME);
771         assertThat(fillRequest.hints.get(1)).isEqualTo(AUTOFILL_HINT_PASSWORD);
772         assertThat(fillRequest.hints.get(2)).isEqualTo(AUTOFILL_HINT_NEW_PASSWORD);
773     }
774 
775 }
776 
777