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.app.Activity.RESULT_CANCELED; 20 import static android.app.Activity.RESULT_OK; 21 import static android.autofillservice.cts.Helper.ID_PASSWORD; 22 import static android.autofillservice.cts.Helper.ID_USERNAME; 23 import static android.autofillservice.cts.Helper.UNUSED_AUTOFILL_VALUE; 24 import static android.autofillservice.cts.Helper.assertTextAndValue; 25 import static android.autofillservice.cts.Helper.findNodeByResourceId; 26 import static android.autofillservice.cts.LoginActivity.getWelcomeMessage; 27 import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD; 28 import static android.view.View.IMPORTANT_FOR_AUTOFILL_NO; 29 30 import static com.google.common.truth.Truth.assertThat; 31 import static com.google.common.truth.Truth.assertWithMessage; 32 33 import android.app.assist.AssistStructure.ViewNode; 34 import android.autofillservice.cts.CannedFillResponse.CannedDataset; 35 import android.autofillservice.cts.InstrumentedAutoFillService.SaveRequest; 36 import android.content.IntentSender; 37 import android.os.Bundle; 38 import android.platform.test.annotations.AppModeFull; 39 import android.support.test.uiautomator.UiObject2; 40 import android.view.View; 41 import android.view.autofill.AutofillValue; 42 43 import org.junit.Test; 44 45 import java.util.concurrent.CountDownLatch; 46 import java.util.regex.Pattern; 47 48 public class AuthenticationTest extends AbstractLoginActivityTestCase { 49 50 @Test 51 public void testDatasetAuthTwoFields() throws Exception { 52 datasetAuthTwoFields(false); 53 } 54 55 @Test 56 @AppModeFull // testDatasetAuthTwoFields() is enough to test ephemeral apps support 57 public void testDatasetAuthTwoFieldsUserCancelsFirstAttempt() throws Exception { 58 datasetAuthTwoFields(true); 59 } 60 61 private void datasetAuthTwoFields(boolean cancelFirstAttempt) throws Exception { 62 // Set service. 63 enableService(); 64 final MyAutofillCallback callback = mActivity.registerCallback(); 65 66 // Prepare the authenticated response 67 final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1, 68 new CannedDataset.Builder() 69 .setField(ID_USERNAME, "dude") 70 .setField(ID_PASSWORD, "sweet") 71 .build()); 72 73 // Configure the service behavior 74 sReplier.addResponse(new CannedFillResponse.Builder() 75 .addDataset(new CannedDataset.Builder() 76 .setField(ID_USERNAME, UNUSED_AUTOFILL_VALUE) 77 .setField(ID_PASSWORD, UNUSED_AUTOFILL_VALUE) 78 .setPresentation(createPresentation("Tap to auth dataset")) 79 .setAuthentication(authentication) 80 .build()) 81 .build()); 82 83 // Set expectation for the activity 84 mActivity.expectAutoFill("dude", "sweet"); 85 86 // Trigger auto-fill. 87 requestFocusOnUsername(); 88 89 // Wait for onFill() before proceeding. 90 sReplier.getNextFillRequest(); 91 final View username = mActivity.getUsername(); 92 callback.assertUiShownEvent(username); 93 mUiBot.assertDatasets("Tap to auth dataset"); 94 95 // Make sure UI is show on 2nd field as well 96 final View password = mActivity.getPassword(); 97 requestFocusOnPassword(); 98 callback.assertUiHiddenEvent(username); 99 callback.assertUiShownEvent(password); 100 mUiBot.assertDatasets("Tap to auth dataset"); 101 102 // Now tap on 1st field to show it again... 103 requestFocusOnUsername(); 104 callback.assertUiHiddenEvent(password); 105 callback.assertUiShownEvent(username); 106 mUiBot.assertDatasets("Tap to auth dataset"); 107 108 if (cancelFirstAttempt) { 109 // Trigger the auth dialog, but emulate cancel. 110 AuthenticationActivity.setResultCode(RESULT_CANCELED); 111 mUiBot.selectDataset("Tap to auth dataset"); 112 callback.assertUiHiddenEvent(username); 113 callback.assertUiShownEvent(username); 114 mUiBot.assertDatasets("Tap to auth dataset"); 115 116 // Make sure it's still shown on other fields... 117 requestFocusOnPassword(); 118 callback.assertUiHiddenEvent(username); 119 callback.assertUiShownEvent(password); 120 mUiBot.assertDatasets("Tap to auth dataset"); 121 122 // Tap on 1st field to show it again... 123 requestFocusOnUsername(); 124 callback.assertUiHiddenEvent(password); 125 callback.assertUiShownEvent(username); 126 } 127 128 // ...and select it this time 129 AuthenticationActivity.setResultCode(RESULT_OK); 130 mUiBot.selectDataset("Tap to auth dataset"); 131 callback.assertUiHiddenEvent(username); 132 mUiBot.assertNoDatasets(); 133 134 // Check the results. 135 mActivity.assertAutoFilled(); 136 } 137 138 @Test 139 @AppModeFull // testDatasetAuthTwoFields() is enough to test ephemeral apps support 140 public void testDatasetAuthTwoFieldsReplaceResponse() throws Exception { 141 // Set service. 142 enableService(); 143 final MyAutofillCallback callback = mActivity.registerCallback(); 144 145 // Prepare the authenticated response 146 final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1, 147 new CannedFillResponse.Builder().addDataset( 148 new CannedDataset.Builder() 149 .setField(ID_USERNAME, "dude") 150 .setField(ID_PASSWORD, "sweet") 151 .setPresentation(createPresentation("Dataset")) 152 .build()) 153 .build()); 154 155 // Set up the authentication response client state 156 final Bundle authentionClientState = new Bundle(); 157 authentionClientState.putCharSequence("clientStateKey1", "clientStateValue1"); 158 159 // Configure the service behavior 160 sReplier.addResponse(new CannedFillResponse.Builder() 161 .addDataset(new CannedDataset.Builder() 162 .setField(ID_USERNAME, (AutofillValue) null) 163 .setField(ID_PASSWORD, (AutofillValue) null) 164 .setPresentation(createPresentation("Tap to auth dataset")) 165 .setAuthentication(authentication) 166 .build()) 167 .setExtras(authentionClientState) 168 .build()); 169 170 // Set expectation for the activity 171 mActivity.expectAutoFill("dude", "sweet"); 172 173 // Trigger auto-fill. 174 requestFocusOnUsername(); 175 176 // Wait for onFill() before proceeding. 177 sReplier.getNextFillRequest(); 178 final View username = mActivity.getUsername(); 179 180 // Authenticate 181 callback.assertUiShownEvent(username); 182 mUiBot.selectDataset("Tap to auth dataset"); 183 callback.assertUiHiddenEvent(username); 184 185 // Select a dataset from the new response 186 callback.assertUiShownEvent(username); 187 mUiBot.selectDataset("Dataset"); 188 callback.assertUiHiddenEvent(username); 189 mUiBot.assertNoDatasets(); 190 191 // Check the results. 192 mActivity.assertAutoFilled(); 193 194 final Bundle data = AuthenticationActivity.getData(); 195 assertThat(data).isNotNull(); 196 final String extraValue = data.getString("clientStateKey1"); 197 assertThat(extraValue).isEqualTo("clientStateValue1"); 198 } 199 200 @Test 201 @AppModeFull // testDatasetAuthTwoFields() is enough to test ephemeral apps support 202 public void testDatasetAuthTwoFieldsNoValues() throws Exception { 203 // Set service. 204 enableService(); 205 final MyAutofillCallback callback = mActivity.registerCallback(); 206 207 // Create the authentication intent 208 final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1, 209 new CannedDataset.Builder() 210 .setField(ID_USERNAME, "dude") 211 .setField(ID_PASSWORD, "sweet") 212 .build()); 213 214 // Configure the service behavior 215 sReplier.addResponse(new CannedFillResponse.Builder() 216 .addDataset(new CannedDataset.Builder() 217 .setField(ID_USERNAME, (String) null) 218 .setField(ID_PASSWORD, (String) null) 219 .setPresentation(createPresentation("Tap to auth dataset")) 220 .setAuthentication(authentication) 221 .build()) 222 .build()); 223 224 // Set expectation for the activity 225 mActivity.expectAutoFill("dude", "sweet"); 226 227 // Trigger auto-fill. 228 requestFocusOnUsername(); 229 230 // Wait for onFill() before proceeding. 231 sReplier.getNextFillRequest(); 232 final View username = mActivity.getUsername(); 233 234 // Authenticate 235 callback.assertUiShownEvent(username); 236 mUiBot.selectDataset("Tap to auth dataset"); 237 callback.assertUiHiddenEvent(username); 238 mUiBot.assertNoDatasets(); 239 240 // Check the results. 241 mActivity.assertAutoFilled(); 242 } 243 244 @Test 245 @AppModeFull // testDatasetAuthTwoFields() is enough to test ephemeral apps support 246 public void testDatasetAuthTwoDatasets() throws Exception { 247 // Set service. 248 enableService(); 249 final MyAutofillCallback callback = mActivity.registerCallback(); 250 251 // Create the authentication intents 252 final CannedDataset unlockedDataset = new CannedDataset.Builder() 253 .setField(ID_USERNAME, "dude") 254 .setField(ID_PASSWORD, "sweet") 255 .build(); 256 final IntentSender authentication1 = AuthenticationActivity.createSender(mContext, 1, 257 unlockedDataset); 258 final IntentSender authentication2 = AuthenticationActivity.createSender(mContext, 2, 259 unlockedDataset); 260 261 // Configure the service behavior 262 sReplier.addResponse(new CannedFillResponse.Builder() 263 .addDataset(new CannedDataset.Builder() 264 .setField(ID_USERNAME, UNUSED_AUTOFILL_VALUE) 265 .setField(ID_PASSWORD, UNUSED_AUTOFILL_VALUE) 266 .setPresentation(createPresentation("Tap to auth dataset 1")) 267 .setAuthentication(authentication1) 268 .build()) 269 .addDataset(new CannedDataset.Builder() 270 .setField(ID_USERNAME, UNUSED_AUTOFILL_VALUE) 271 .setField(ID_PASSWORD, UNUSED_AUTOFILL_VALUE) 272 .setPresentation(createPresentation("Tap to auth dataset 2")) 273 .setAuthentication(authentication2) 274 .build()) 275 .build()); 276 277 // Set expectation for the activity 278 mActivity.expectAutoFill("dude", "sweet"); 279 280 // Trigger auto-fill. 281 requestFocusOnUsername(); 282 283 // Wait for onFill() before proceeding. 284 sReplier.getNextFillRequest(); 285 final View username = mActivity.getUsername(); 286 287 // Authenticate 288 callback.assertUiShownEvent(username); 289 mUiBot.assertDatasets("Tap to auth dataset 1", "Tap to auth dataset 2"); 290 291 mUiBot.selectDataset("Tap to auth dataset 1"); 292 callback.assertUiHiddenEvent(username); 293 mUiBot.assertNoDatasets(); 294 295 // Check the results. 296 mActivity.assertAutoFilled(); 297 } 298 299 @Test 300 @AppModeFull // testDatasetAuthTwoFields() is enough to test ephemeral apps support 301 public void testDatasetAuthMixedSelectAuth() throws Exception { 302 datasetAuthMixedTest(true); 303 } 304 305 @Test 306 @AppModeFull // testDatasetAuthTwoFields() is enough to test ephemeral apps support 307 public void testDatasetAuthMixedSelectNonAuth() throws Exception { 308 datasetAuthMixedTest(false); 309 } 310 311 private void datasetAuthMixedTest(boolean selectAuth) throws Exception { 312 // Set service. 313 enableService(); 314 final MyAutofillCallback callback = mActivity.registerCallback(); 315 316 // Prepare the authenticated response 317 final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1, 318 new CannedDataset.Builder() 319 .setField(ID_USERNAME, "dude") 320 .setField(ID_PASSWORD, "sweet") 321 .build()); 322 323 // Configure the service behavior 324 sReplier.addResponse(new CannedFillResponse.Builder() 325 .addDataset(new CannedDataset.Builder() 326 .setField(ID_USERNAME, "dude") 327 .setField(ID_PASSWORD, "sweet") 328 .setPresentation(createPresentation("Tap to auth dataset")) 329 .setAuthentication(authentication) 330 .build()) 331 .addDataset(new CannedDataset.Builder() 332 .setField(ID_USERNAME, "DUDE") 333 .setField(ID_PASSWORD, "SWEET") 334 .setPresentation(createPresentation("What, me auth?")) 335 .build()) 336 .build()); 337 338 // Set expectation for the activity 339 if (selectAuth) { 340 mActivity.expectAutoFill("dude", "sweet"); 341 } else { 342 mActivity.expectAutoFill("DUDE", "SWEET"); 343 } 344 345 // Trigger auto-fill. 346 requestFocusOnUsername(); 347 348 // Wait for onFill() before proceeding. 349 sReplier.getNextFillRequest(); 350 final View username = mActivity.getUsername(); 351 352 // Authenticate 353 callback.assertUiShownEvent(username); 354 mUiBot.assertDatasets("Tap to auth dataset", "What, me auth?"); 355 356 final String chosenOne = selectAuth ? "Tap to auth dataset" : "What, me auth?"; 357 mUiBot.selectDataset(chosenOne); 358 callback.assertUiHiddenEvent(username); 359 mUiBot.assertNoDatasets(); 360 361 // Check the results. 362 mActivity.assertAutoFilled(); 363 } 364 365 @Test 366 @AppModeFull // testDatasetAuthFilteringUsingRegex() is enough to test ephemeral apps support 367 public void testDatasetAuthNoFiltering() throws Exception { 368 // Set service. 369 enableService(); 370 final MyAutofillCallback callback = mActivity.registerCallback(); 371 372 // Create the authentication intents 373 final CannedDataset unlockedDataset = new CannedDataset.Builder() 374 .setField(ID_USERNAME, "dude") 375 .setField(ID_PASSWORD, "sweet") 376 .build(); 377 final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1, 378 unlockedDataset); 379 380 // Configure the service behavior 381 sReplier.addResponse(new CannedFillResponse.Builder() 382 .addDataset(new CannedDataset.Builder() 383 .setField(ID_USERNAME, UNUSED_AUTOFILL_VALUE) 384 .setField(ID_PASSWORD, UNUSED_AUTOFILL_VALUE) 385 .setPresentation(createPresentation("Tap to auth dataset")) 386 .setAuthentication(authentication) 387 .build()) 388 .build()); 389 390 // Set expectation for the activity 391 mActivity.expectAutoFill("dude", "sweet"); 392 393 // Trigger auto-fill. 394 requestFocusOnUsername(); 395 396 // Wait for onFill() before proceeding. 397 sReplier.getNextFillRequest(); 398 final View username = mActivity.getUsername(); 399 400 // Make sure it's showing initially... 401 callback.assertUiShownEvent(username); 402 mUiBot.assertDatasets("Tap to auth dataset"); 403 404 // ..then type something to hide it. 405 mActivity.onUsername((v) -> v.setText("a")); 406 callback.assertUiHiddenEvent(username); 407 mUiBot.assertNoDatasets(); 408 409 // Now delete the char and assert it's shown again... 410 mActivity.onUsername((v) -> v.setText("")); 411 callback.assertUiShownEvent(username); 412 mUiBot.assertDatasets("Tap to auth dataset"); 413 414 // ...and select it this time 415 mUiBot.selectDataset("Tap to auth dataset"); 416 callback.assertUiHiddenEvent(username); 417 mUiBot.assertNoDatasets(); 418 419 // Check the results. 420 mActivity.assertAutoFilled(); 421 } 422 423 @Test 424 @AppModeFull // testDatasetAuthFilteringUsingRegex() is enough to test ephemeral apps support 425 public void testDatasetAuthFilteringUsingAutofillValue() throws Exception { 426 // Set service. 427 enableService(); 428 final MyAutofillCallback callback = mActivity.registerCallback(); 429 430 // Create the authentication intents 431 final CannedDataset unlockedDataset = new CannedDataset.Builder() 432 .setField(ID_USERNAME, "dude") 433 .setField(ID_PASSWORD, "sweet") 434 .build(); 435 final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1, 436 unlockedDataset); 437 438 // Configure the service behavior 439 sReplier.addResponse(new CannedFillResponse.Builder() 440 .addDataset(new CannedDataset.Builder() 441 .setField(ID_USERNAME, "dude") 442 .setField(ID_PASSWORD, "sweet") 443 .setPresentation(createPresentation("DS1")) 444 .setAuthentication(authentication) 445 .build()) 446 .addDataset(new CannedDataset.Builder() 447 .setField(ID_USERNAME, "DUDE,THE") 448 .setField(ID_PASSWORD, "SWEET") 449 .setPresentation(createPresentation("DS2")) 450 .setAuthentication(authentication) 451 .build()) 452 .addDataset(new CannedDataset.Builder() 453 .setField(ID_USERNAME, "ZzBottom") 454 .setField(ID_PASSWORD, "top") 455 .setPresentation(createPresentation("DS3")) 456 .setAuthentication(authentication) 457 .build()) 458 .build()); 459 460 // Set expectation for the activity 461 mActivity.expectAutoFill("dude", "sweet"); 462 463 // Trigger auto-fill. 464 requestFocusOnUsername(); 465 466 // Wait for onFill() before proceeding. 467 sReplier.getNextFillRequest(); 468 final View username = mActivity.getUsername(); 469 470 // Make sure it's showing initially... 471 callback.assertUiShownEvent(username); 472 mUiBot.assertDatasets("DS1", "DS2", "DS3"); 473 474 // ...then type something to hide them. 475 mActivity.onUsername((v) -> v.setText("a")); 476 callback.assertUiHiddenEvent(username); 477 mUiBot.assertNoDatasets(); 478 479 // Now delete the char and assert they're shown again... 480 mActivity.onUsername((v) -> v.setText("")); 481 callback.assertUiShownEvent(username); 482 mUiBot.assertDatasets("DS1", "DS2", "DS3"); 483 484 // ...then filter for 2 485 mActivity.onUsername((v) -> v.setText("d")); 486 mUiBot.assertDatasets("DS1", "DS2"); 487 488 // ...up to 1 489 mActivity.onUsername((v) -> v.setText("du")); 490 mUiBot.assertDatasets("DS1", "DS2"); 491 mActivity.onUsername((v) -> v.setText("dud")); 492 mUiBot.assertDatasets("DS1", "DS2"); 493 mActivity.onUsername((v) -> v.setText("dude")); 494 mUiBot.assertDatasets("DS1", "DS2"); 495 mActivity.onUsername((v) -> v.setText("dude,")); 496 mUiBot.assertDatasets("DS2"); 497 498 // Now delete the char and assert 2 are shown again... 499 mActivity.onUsername((v) -> v.setText("dude")); 500 final UiObject2 picker = mUiBot.assertDatasets("DS1", "DS2"); 501 502 // ...and select it this time 503 mUiBot.selectDataset(picker, "DS1"); 504 callback.assertUiHiddenEvent(username); 505 mUiBot.assertNoDatasets(); 506 507 // Check the results. 508 mActivity.assertAutoFilled(); 509 } 510 511 @Test 512 public void testDatasetAuthFilteringUsingRegex() throws Exception { 513 // Set service. 514 enableService(); 515 final MyAutofillCallback callback = mActivity.registerCallback(); 516 517 // Create the authentication intents 518 final CannedDataset unlockedDataset = new CannedDataset.Builder() 519 .setField(ID_USERNAME, "dude") 520 .setField(ID_PASSWORD, "sweet") 521 .build(); 522 final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1, 523 unlockedDataset); 524 525 // Configure the service behavior 526 527 final Pattern min2Chars = Pattern.compile(".{2,}"); 528 sReplier.addResponse(new CannedFillResponse.Builder() 529 .addDataset(new CannedDataset.Builder() 530 .setField(ID_USERNAME, UNUSED_AUTOFILL_VALUE, min2Chars) 531 .setField(ID_PASSWORD, UNUSED_AUTOFILL_VALUE) 532 .setPresentation(createPresentation("Tap to auth dataset")) 533 .setAuthentication(authentication) 534 .build()) 535 .build()); 536 537 // Set expectation for the activity 538 mActivity.expectAutoFill("dude", "sweet"); 539 540 // Trigger auto-fill. 541 requestFocusOnUsername(); 542 543 // Wait for onFill() before proceeding. 544 sReplier.getNextFillRequest(); 545 final View username = mActivity.getUsername(); 546 547 // Make sure it's showing initially... 548 callback.assertUiShownEvent(username); 549 mUiBot.assertDatasets("Tap to auth dataset"); 550 551 // ...then type something to hide it. 552 mActivity.onUsername((v) -> v.setText("a")); 553 callback.assertUiHiddenEvent(username); 554 mUiBot.assertNoDatasets(); 555 556 // ...now type something again to show it, as the input will have 2 chars. 557 mActivity.onUsername((v) -> v.setText("aa")); 558 callback.assertUiShownEvent(username); 559 mUiBot.assertDatasets("Tap to auth dataset"); 560 561 // Delete the char and assert it's not shown again... 562 mActivity.onUsername((v) -> v.setText("a")); 563 callback.assertUiHiddenEvent(username); 564 mUiBot.assertNoDatasets(); 565 566 // ...then type something again to show it, as the input will have 2 chars. 567 mActivity.onUsername((v) -> v.setText("aa")); 568 callback.assertUiShownEvent(username); 569 570 // ...and select it this time 571 mUiBot.selectDataset("Tap to auth dataset"); 572 callback.assertUiHiddenEvent(username); 573 mUiBot.assertNoDatasets(); 574 575 // Check the results. 576 mActivity.assertAutoFilled(); 577 } 578 579 @Test 580 @AppModeFull // testDatasetAuthFilteringUsingRegex() is enough to test ephemeral apps support 581 public void testDatasetAuthMixedFilteringSelectAuth() throws Exception { 582 datasetAuthMixedFilteringTest(true); 583 } 584 585 @Test 586 @AppModeFull // testDatasetAuthFilteringUsingRegex() is enough to test ephemeral apps support 587 public void testDatasetAuthMixedFilteringSelectNonAuth() throws Exception { 588 datasetAuthMixedFilteringTest(false); 589 } 590 591 private void datasetAuthMixedFilteringTest(boolean selectAuth) throws Exception { 592 // Set service. 593 enableService(); 594 final MyAutofillCallback callback = mActivity.registerCallback(); 595 596 // Create the authentication intents 597 final CannedDataset unlockedDataset = new CannedDataset.Builder() 598 .setField(ID_USERNAME, "DUDE") 599 .setField(ID_PASSWORD, "SWEET") 600 .build(); 601 final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1, 602 unlockedDataset); 603 604 // Configure the service behavior 605 sReplier.addResponse(new CannedFillResponse.Builder() 606 .addDataset(new CannedDataset.Builder() 607 .setField(ID_USERNAME, UNUSED_AUTOFILL_VALUE) 608 .setField(ID_PASSWORD, UNUSED_AUTOFILL_VALUE) 609 .setPresentation(createPresentation("Tap to auth dataset")) 610 .setAuthentication(authentication) 611 .build()) 612 .addDataset(new CannedDataset.Builder() 613 .setField(ID_USERNAME, "dude") 614 .setField(ID_PASSWORD, "sweet") 615 .setPresentation(createPresentation("What, me auth?")) 616 .build()) 617 .build()); 618 619 // Set expectation for the activity 620 if (selectAuth) { 621 mActivity.expectAutoFill("DUDE", "SWEET"); 622 } else { 623 mActivity.expectAutoFill("dude", "sweet"); 624 } 625 626 // Trigger auto-fill. 627 requestFocusOnUsername(); 628 629 // Wait for onFill() before proceeding. 630 sReplier.getNextFillRequest(); 631 final View username = mActivity.getUsername(); 632 633 // Make sure it's showing initially... 634 callback.assertUiShownEvent(username); 635 mUiBot.assertDatasets("Tap to auth dataset", "What, me auth?"); 636 637 // Filter the auth dataset. 638 mActivity.onUsername((v) -> v.setText("d")); 639 mUiBot.assertDatasets("What, me auth?"); 640 641 // Filter all. 642 mActivity.onUsername((v) -> v.setText("dw")); 643 callback.assertUiHiddenEvent(username); 644 mUiBot.assertNoDatasets(); 645 646 // Now delete the char and assert the non-auth is shown again. 647 mActivity.onUsername((v) -> v.setText("d")); 648 callback.assertUiShownEvent(username); 649 mUiBot.assertDatasets("What, me auth?"); 650 651 // Delete again and assert all dataset are shown. 652 mActivity.onUsername((v) -> v.setText("")); 653 mUiBot.assertDatasets("Tap to auth dataset", "What, me auth?"); 654 655 // ...and select it this time 656 final String chosenOne = selectAuth ? "Tap to auth dataset" : "What, me auth?"; 657 mUiBot.selectDataset(chosenOne); 658 callback.assertUiHiddenEvent(username); 659 mUiBot.assertNoDatasets(); 660 661 // Check the results. 662 mActivity.assertAutoFilled(); 663 } 664 665 @Test 666 public void testDatasetAuthClientStateSetOnIntentOnly() throws Exception { 667 fillDatasetAuthWithClientState(ClientStateLocation.INTENT_ONLY); 668 } 669 670 @Test 671 @AppModeFull // testDatasetAuthClientStateSetOnIntentOnly() is enough to test ephemeral apps 672 public void testDatasetAuthClientStateSetOnFillResponseOnly() throws Exception { 673 fillDatasetAuthWithClientState(ClientStateLocation.FILL_RESPONSE_ONLY); 674 } 675 676 @Test 677 @AppModeFull // testDatasetAuthClientStateSetOnIntentOnly() is enough to test ephemeral apps 678 public void testDatasetAuthClientStateSetOnIntentAndFillResponse() throws Exception { 679 fillDatasetAuthWithClientState(ClientStateLocation.BOTH); 680 } 681 682 private void fillDatasetAuthWithClientState(ClientStateLocation where) throws Exception { 683 // Set service. 684 enableService(); 685 686 // Prepare the authenticated response 687 final CannedDataset dataset = new CannedDataset.Builder() 688 .setField(ID_USERNAME, "dude") 689 .setField(ID_PASSWORD, "sweet") 690 .build(); 691 final IntentSender authentication = where == ClientStateLocation.FILL_RESPONSE_ONLY 692 ? AuthenticationActivity.createSender(mContext, 1, 693 dataset) 694 : AuthenticationActivity.createSender(mContext, 1, 695 dataset, newClientState("CSI", "FromIntent")); 696 697 // Configure the service behavior 698 sReplier.addResponse(new CannedFillResponse.Builder() 699 .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD) 700 .setExtras(newClientState("CSI", "FromResponse")) 701 .addDataset(new CannedDataset.Builder() 702 .setField(ID_USERNAME, UNUSED_AUTOFILL_VALUE) 703 .setField(ID_PASSWORD, UNUSED_AUTOFILL_VALUE) 704 .setPresentation(createPresentation("Tap to auth dataset")) 705 .setAuthentication(authentication) 706 .build()) 707 .build()); 708 709 // Set expectation for the activity 710 mActivity.expectAutoFill("dude", "sweet"); 711 712 // Trigger auto-fill. 713 requestFocusOnUsername(); 714 sReplier.getNextFillRequest(); 715 716 // Tap authentication request. 717 mUiBot.selectDataset("Tap to auth dataset"); 718 719 // Check the results. 720 mActivity.assertAutoFilled(); 721 722 // Now trigger save. 723 mActivity.onUsername((v) -> v.setText("malkovich")); 724 mActivity.onPassword((v) -> v.setText("malkovich")); 725 final String expectedMessage = getWelcomeMessage("malkovich"); 726 final String actualMessage = mActivity.tapLogin(); 727 assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage); 728 mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD); 729 730 // Assert client state on authentication activity. 731 assertClientState("auth activity", AuthenticationActivity.getData(), "CSI", "FromResponse"); 732 733 // Assert client state on save request. 734 final SaveRequest saveRequest = sReplier.getNextSaveRequest(); 735 final String expectedValue = where == ClientStateLocation.FILL_RESPONSE_ONLY 736 ? "FromResponse" : "FromIntent"; 737 assertClientState("on save", saveRequest.data, "CSI", expectedValue); 738 } 739 740 @Test 741 public void testFillResponseAuthBothFields() throws Exception { 742 fillResponseAuthBothFields(false); 743 } 744 745 @Test 746 @AppModeFull // testFillResponseAuthBothFields() is enough to test ephemeral apps support 747 public void testFillResponseAuthBothFieldsUserCancelsFirstAttempt() throws Exception { 748 fillResponseAuthBothFields(true); 749 } 750 751 private void fillResponseAuthBothFields(boolean cancelFirstAttempt) throws Exception { 752 // Set service. 753 enableService(); 754 final MyAutofillCallback callback = mActivity.registerCallback(); 755 756 // Prepare the authenticated response 757 final Bundle clientState = new Bundle(); 758 clientState.putString("numbers", "4815162342"); 759 final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1, 760 new CannedFillResponse.Builder().addDataset( 761 new CannedDataset.Builder() 762 .setField(ID_USERNAME, "dude") 763 .setField(ID_PASSWORD, "sweet") 764 .setId("name") 765 .setPresentation(createPresentation("Dataset")) 766 .build()) 767 .setExtras(clientState).build()); 768 769 // Configure the service behavior 770 sReplier.addResponse(new CannedFillResponse.Builder() 771 .setAuthentication(authentication, ID_USERNAME, ID_PASSWORD) 772 .setPresentation(createPresentation("Tap to auth response")) 773 .setExtras(clientState) 774 .build()); 775 776 // Set expectation for the activity 777 mActivity.expectAutoFill("dude", "sweet"); 778 779 // Trigger auto-fill. 780 requestFocusOnUsername(); 781 782 // Wait for onFill() before proceeding. 783 sReplier.getNextFillRequest(); 784 final View username = mActivity.getUsername(); 785 callback.assertUiShownEvent(username); 786 mUiBot.assertDatasets("Tap to auth response"); 787 788 // Make sure UI is show on 2nd field as well 789 final View password = mActivity.getPassword(); 790 requestFocusOnPassword(); 791 callback.assertUiHiddenEvent(username); 792 callback.assertUiShownEvent(password); 793 mUiBot.assertDatasets("Tap to auth response"); 794 795 // Now tap on 1st field to show it again... 796 requestFocusOnUsername(); 797 callback.assertUiHiddenEvent(password); 798 callback.assertUiShownEvent(username); 799 800 if (cancelFirstAttempt) { 801 // Trigger the auth dialog, but emulate cancel. 802 AuthenticationActivity.setResultCode(RESULT_CANCELED); 803 mUiBot.selectDataset("Tap to auth response"); 804 callback.assertUiHiddenEvent(username); 805 callback.assertUiShownEvent(username); 806 mUiBot.assertDatasets("Tap to auth response"); 807 808 // Make sure it's still shown on other fields... 809 requestFocusOnPassword(); 810 callback.assertUiHiddenEvent(username); 811 callback.assertUiShownEvent(password); 812 mUiBot.assertDatasets("Tap to auth response"); 813 814 // Tap on 1st field to show it again... 815 requestFocusOnUsername(); 816 callback.assertUiHiddenEvent(password); 817 callback.assertUiShownEvent(username); 818 } 819 820 // ...and select it this time 821 AuthenticationActivity.setResultCode(RESULT_OK); 822 mUiBot.selectDataset("Tap to auth response"); 823 callback.assertUiHiddenEvent(username); 824 callback.assertUiShownEvent(username); 825 final UiObject2 picker = mUiBot.assertDatasets("Dataset"); 826 mUiBot.selectDataset(picker, "Dataset"); 827 callback.assertUiHiddenEvent(username); 828 mUiBot.assertNoDatasets(); 829 830 // Check the results. 831 mActivity.assertAutoFilled(); 832 833 final Bundle data = AuthenticationActivity.getData(); 834 assertThat(data).isNotNull(); 835 final String extraValue = data.getString("numbers"); 836 assertThat(extraValue).isEqualTo("4815162342"); 837 } 838 839 @Test 840 @AppModeFull // testFillResponseAuthBothFields() is enough to test ephemeral apps support 841 public void testFillResponseAuthJustOneField() throws Exception { 842 // Set service. 843 enableService(); 844 final MyAutofillCallback callback = mActivity.registerCallback(); 845 846 // Prepare the authenticated response 847 final Bundle clientState = new Bundle(); 848 clientState.putString("numbers", "4815162342"); 849 final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1, 850 new CannedFillResponse.Builder().addDataset( 851 new CannedDataset.Builder() 852 .setField(ID_USERNAME, "dude") 853 .setField(ID_PASSWORD, "sweet") 854 .setPresentation(createPresentation("Dataset")) 855 .build()) 856 .build()); 857 858 // Configure the service behavior 859 sReplier.addResponse(new CannedFillResponse.Builder() 860 .setAuthentication(authentication, ID_USERNAME) 861 .setIgnoreFields(ID_PASSWORD) 862 .setPresentation(createPresentation("Tap to auth response")) 863 .setExtras(clientState) 864 .build()); 865 866 // Set expectation for the activity 867 mActivity.expectAutoFill("dude", "sweet"); 868 869 // Trigger auto-fill. 870 requestFocusOnUsername(); 871 872 // Wait for onFill() before proceeding. 873 sReplier.getNextFillRequest(); 874 final View username = mActivity.getUsername(); 875 callback.assertUiShownEvent(username); 876 mUiBot.assertDatasets("Tap to auth response"); 877 878 // Make sure UI is not show on 2nd field 879 requestFocusOnPassword(); 880 callback.assertUiHiddenEvent(username); 881 mUiBot.assertNoDatasets(); 882 // Now tap on 1st field to show it again... 883 requestFocusOnUsername(); 884 callback.assertUiShownEvent(username); 885 886 // ...and select it this time 887 mUiBot.selectDataset("Tap to auth response"); 888 callback.assertUiHiddenEvent(username); 889 final UiObject2 picker = mUiBot.assertDatasets("Dataset"); 890 891 callback.assertUiShownEvent(username); 892 mUiBot.selectDataset(picker, "Dataset"); 893 callback.assertUiHiddenEvent(username); 894 mUiBot.assertNoDatasets(); 895 896 // Check the results. 897 mActivity.assertAutoFilled(); 898 final Bundle data = AuthenticationActivity.getData(); 899 assertThat(data).isNotNull(); 900 final String extraValue = data.getString("numbers"); 901 assertThat(extraValue).isEqualTo("4815162342"); 902 } 903 904 @Test 905 @AppModeFull // testFillResponseAuthBothFields() is enough to test ephemeral apps support 906 public void testFillResponseAuthWhenAppCallsCancel() throws Exception { 907 // Set service. 908 enableService(); 909 final MyAutofillCallback callback = mActivity.registerCallback(); 910 911 // Prepare the authenticated response 912 final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1, 913 new CannedFillResponse.Builder().addDataset( 914 new CannedDataset.Builder() 915 .setField(ID_USERNAME, "dude") 916 .setField(ID_PASSWORD, "sweet") 917 .setId("name") 918 .setPresentation(createPresentation("Dataset")) 919 .build()) 920 .build()); 921 922 // Configure the service behavior 923 sReplier.addResponse(new CannedFillResponse.Builder() 924 .setAuthentication(authentication, ID_USERNAME, ID_PASSWORD) 925 .setPresentation(createPresentation("Tap to auth response")) 926 .build()); 927 928 // Trigger autofill. 929 requestFocusOnUsername(); 930 931 // Wait for onFill() before proceeding. 932 sReplier.getNextFillRequest(); 933 final View username = mActivity.getUsername(); 934 callback.assertUiShownEvent(username); 935 mUiBot.assertDatasets("Tap to auth response"); 936 937 // Disables autofill so it's not triggered again after the auth activity is finished 938 // (and current session is canceled) and the login activity is resumed. 939 username.setImportantForAutofill(IMPORTANT_FOR_AUTOFILL_NO); 940 941 // Autofill it. 942 final CountDownLatch latch = new CountDownLatch(1); 943 AuthenticationActivity.setResultCode(latch, RESULT_OK); 944 945 mUiBot.selectDataset("Tap to auth response"); 946 callback.assertUiHiddenEvent(username); 947 948 // Cancel session... 949 mActivity.getAutofillManager().cancel(); 950 951 // ...before finishing the Auth UI. 952 latch.countDown(); 953 954 mUiBot.assertNoDatasets(); 955 } 956 957 @Test 958 @AppModeFull // testFillResponseAuthBothFields() is enough to test ephemeral apps support 959 public void testFillResponseAuthServiceHasNoDataButCanSave() throws Exception { 960 fillResponseAuthServiceHasNoDataTest(true); 961 } 962 963 @Test 964 @AppModeFull // testFillResponseAuthBothFields() is enough to test ephemeral apps support 965 public void testFillResponseAuthServiceHasNoData() throws Exception { 966 fillResponseAuthServiceHasNoDataTest(false); 967 } 968 969 private void fillResponseAuthServiceHasNoDataTest(boolean canSave) throws Exception { 970 // Set service. 971 enableService(); 972 final MyAutofillCallback callback = mActivity.registerCallback(); 973 974 // Prepare the authenticated response 975 final CannedFillResponse response = canSave 976 ? new CannedFillResponse.Builder() 977 .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD) 978 .build() 979 : CannedFillResponse.NO_RESPONSE; 980 981 final IntentSender authentication = 982 AuthenticationActivity.createSender(mContext, 1, response); 983 984 // Configure the service behavior 985 sReplier.addResponse(new CannedFillResponse.Builder() 986 .setAuthentication(authentication, ID_USERNAME, ID_PASSWORD) 987 .setPresentation(createPresentation("Tap to auth response")) 988 .build()); 989 990 // Trigger auto-fill. 991 requestFocusOnUsername(); 992 993 // Wait for onFill() before proceeding. 994 sReplier.getNextFillRequest(); 995 final View username = mActivity.getUsername(); 996 callback.assertUiShownEvent(username); 997 998 // Select the authentication dialog. 999 mUiBot.selectDataset("Tap to auth response"); 1000 callback.assertUiHiddenEvent(username); 1001 mUiBot.assertNoDatasets(); 1002 1003 if (!canSave) { 1004 // Our work is done! 1005 return; 1006 } 1007 1008 // Set credentials... 1009 mActivity.onUsername((v) -> v.setText("malkovich")); 1010 mActivity.onPassword((v) -> v.setText("malkovich")); 1011 1012 // ...and login 1013 final String expectedMessage = getWelcomeMessage("malkovich"); 1014 final String actualMessage = mActivity.tapLogin(); 1015 assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage); 1016 1017 // Assert the snack bar is shown and tap "Save". 1018 mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD); 1019 1020 final SaveRequest saveRequest = sReplier.getNextSaveRequest(); 1021 sReplier.assertNoUnhandledSaveRequests(); 1022 assertThat(saveRequest.datasetIds).isNull(); 1023 1024 // Assert value of expected fields - should not be sanitized. 1025 final ViewNode usernameNode = findNodeByResourceId(saveRequest.structure, ID_USERNAME); 1026 assertTextAndValue(usernameNode, "malkovich"); 1027 final ViewNode passwordNode = findNodeByResourceId(saveRequest.structure, ID_PASSWORD); 1028 assertTextAndValue(passwordNode, "malkovich"); 1029 } 1030 1031 @Test 1032 public void testFillResponseAuthClientStateSetOnIntentOnly() throws Exception { 1033 fillResponseAuthWithClientState(ClientStateLocation.INTENT_ONLY); 1034 } 1035 1036 @Test 1037 @AppModeFull // testFillResponseAuthClientStateSetOnIntentOnly() is enough to test ephemeral 1038 public void testFillResponseAuthClientStateSetOnFillResponseOnly() throws Exception { 1039 fillResponseAuthWithClientState(ClientStateLocation.FILL_RESPONSE_ONLY); 1040 } 1041 1042 @Test 1043 @AppModeFull // testFillResponseAuthClientStateSetOnIntentOnly() is enough to test ephemeral 1044 public void testFillResponseAuthClientStateSetOnIntentAndFillResponse() throws Exception { 1045 fillResponseAuthWithClientState(ClientStateLocation.BOTH); 1046 } 1047 1048 enum ClientStateLocation { 1049 INTENT_ONLY, 1050 FILL_RESPONSE_ONLY, 1051 BOTH 1052 } 1053 1054 private void fillResponseAuthWithClientState(ClientStateLocation where) throws Exception { 1055 // Set service. 1056 enableService(); 1057 1058 // Prepare the authenticated response 1059 final CannedFillResponse.Builder authenticatedResponseBuilder = 1060 new CannedFillResponse.Builder() 1061 .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD) 1062 .addDataset(new CannedDataset.Builder() 1063 .setField(ID_USERNAME, "dude") 1064 .setField(ID_PASSWORD, "sweet") 1065 .setPresentation(createPresentation("Dataset")) 1066 .build()); 1067 1068 if (where == ClientStateLocation.FILL_RESPONSE_ONLY || where == ClientStateLocation.BOTH) { 1069 authenticatedResponseBuilder.setExtras(newClientState("CSI", "FromAuthResponse")); 1070 } 1071 1072 final IntentSender authentication = where == ClientStateLocation.FILL_RESPONSE_ONLY 1073 ? AuthenticationActivity.createSender(mContext, 1, 1074 authenticatedResponseBuilder.build()) 1075 : AuthenticationActivity.createSender(mContext, 1, 1076 authenticatedResponseBuilder.build(), newClientState("CSI", "FromIntent")); 1077 1078 // Configure the service behavior 1079 sReplier.addResponse(new CannedFillResponse.Builder() 1080 .setAuthentication(authentication, ID_USERNAME) 1081 .setIgnoreFields(ID_PASSWORD) 1082 .setPresentation(createPresentation("Tap to auth response")) 1083 .setExtras(newClientState("CSI", "FromResponse")) 1084 .build()); 1085 1086 // Set expectation for the activity 1087 mActivity.expectAutoFill("dude", "sweet"); 1088 1089 // Trigger autofill. 1090 requestFocusOnUsername(); 1091 sReplier.getNextFillRequest(); 1092 1093 // Tap authentication request. 1094 mUiBot.selectDataset("Tap to auth response"); 1095 1096 // Tap dataset. 1097 mUiBot.selectDataset("Dataset"); 1098 1099 // Check the results. 1100 mActivity.assertAutoFilled(); 1101 1102 // Now trigger save. 1103 mActivity.onUsername((v) -> v.setText("malkovich")); 1104 mActivity.onPassword((v) -> v.setText("malkovich")); 1105 final String expectedMessage = getWelcomeMessage("malkovich"); 1106 final String actualMessage = mActivity.tapLogin(); 1107 assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage); 1108 mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD); 1109 1110 // Assert client state on authentication activity. 1111 assertClientState("auth activity", AuthenticationActivity.getData(), "CSI", "FromResponse"); 1112 1113 // Assert client state on save request. 1114 final SaveRequest saveRequest = sReplier.getNextSaveRequest(); 1115 final String expectedValue = where == ClientStateLocation.FILL_RESPONSE_ONLY 1116 ? "FromAuthResponse" : "FromIntent"; 1117 assertClientState("on save", saveRequest.data, "CSI", expectedValue); 1118 } 1119 1120 // TODO(on master): move to helper / reuse in other places 1121 private void assertClientState(String where, Bundle data, String expectedKey, 1122 String expectedValue) { 1123 assertWithMessage("no client state on %s", where).that(data).isNotNull(); 1124 final String extraValue = data.getString(expectedKey); 1125 assertWithMessage("invalid value for %s on %s", expectedKey, where) 1126 .that(extraValue).isEqualTo(expectedValue); 1127 } 1128 1129 // TODO(on master): move to helper / reuse in other places 1130 private Bundle newClientState(String key, String value) { 1131 final Bundle clientState = new Bundle(); 1132 clientState.putString(key, value); 1133 return clientState; 1134 } 1135 1136 @Test 1137 public void testFillResponseFiltering() throws Exception { 1138 // Set service. 1139 enableService(); 1140 final MyAutofillCallback callback = mActivity.registerCallback(); 1141 1142 // Prepare the authenticated response 1143 final Bundle clientState = new Bundle(); 1144 clientState.putString("numbers", "4815162342"); 1145 final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1, 1146 new CannedFillResponse.Builder().addDataset( 1147 new CannedDataset.Builder() 1148 .setField(ID_USERNAME, "dude") 1149 .setField(ID_PASSWORD, "sweet") 1150 .setId("name") 1151 .setPresentation(createPresentation("Dataset")) 1152 .build()) 1153 .setExtras(clientState).build()); 1154 1155 // Configure the service behavior 1156 sReplier.addResponse(new CannedFillResponse.Builder() 1157 .setAuthentication(authentication, ID_USERNAME, ID_PASSWORD) 1158 .setPresentation(createPresentation("Tap to auth response")) 1159 .setExtras(clientState) 1160 .build()); 1161 1162 // Set expectation for the activity 1163 mActivity.expectAutoFill("dude", "sweet"); 1164 1165 // Trigger auto-fill. 1166 requestFocusOnUsername(); 1167 1168 // Wait for onFill() before proceeding. 1169 sReplier.getNextFillRequest(); 1170 final View username = mActivity.getUsername(); 1171 1172 // Make sure it's showing initially... 1173 callback.assertUiShownEvent(username); 1174 mUiBot.assertDatasets("Tap to auth response"); 1175 1176 // ..then type something to hide it. 1177 mActivity.onUsername((v) -> v.setText("a")); 1178 callback.assertUiHiddenEvent(username); 1179 mUiBot.assertNoDatasets(); 1180 1181 // Now delete the char and assert it's shown again... 1182 mActivity.onUsername((v) -> v.setText("")); 1183 callback.assertUiShownEvent(username); 1184 mUiBot.assertDatasets("Tap to auth response"); 1185 1186 // ...and select it this time 1187 AuthenticationActivity.setResultCode(RESULT_OK); 1188 mUiBot.selectDataset("Tap to auth response"); 1189 callback.assertUiHiddenEvent(username); 1190 callback.assertUiShownEvent(username); 1191 final UiObject2 picker = mUiBot.assertDatasets("Dataset"); 1192 mUiBot.selectDataset(picker, "Dataset"); 1193 callback.assertUiHiddenEvent(username); 1194 mUiBot.assertNoDatasets(); 1195 1196 // Check the results. 1197 mActivity.assertAutoFilled(); 1198 1199 final Bundle data = AuthenticationActivity.getData(); 1200 assertThat(data).isNotNull(); 1201 final String extraValue = data.getString("numbers"); 1202 assertThat(extraValue).isEqualTo("4815162342"); 1203 } 1204 } 1205