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