1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package android.autofillservice.cts; 17 18 import static android.autofillservice.cts.AntiTrimmerTextWatcher.TRIMMER_PATTERN; 19 import static android.autofillservice.cts.Helper.ID_STATIC_TEXT; 20 import static android.autofillservice.cts.Helper.LARGE_STRING; 21 import static android.autofillservice.cts.Helper.assertTextAndValue; 22 import static android.autofillservice.cts.Helper.assertTextValue; 23 import static android.autofillservice.cts.Helper.findAutofillIdByResourceId; 24 import static android.autofillservice.cts.Helper.findNodeByResourceId; 25 import static android.autofillservice.cts.LoginActivity.ID_USERNAME_CONTAINER; 26 import static android.autofillservice.cts.SimpleSaveActivity.ID_COMMIT; 27 import static android.autofillservice.cts.SimpleSaveActivity.ID_INPUT; 28 import static android.autofillservice.cts.SimpleSaveActivity.ID_LABEL; 29 import static android.autofillservice.cts.SimpleSaveActivity.ID_PASSWORD; 30 import static android.autofillservice.cts.SimpleSaveActivity.TEXT_LABEL; 31 import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_GENERIC; 32 import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD; 33 import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_USERNAME; 34 35 import static com.google.common.truth.Truth.assertThat; 36 import static com.google.common.truth.Truth.assertWithMessage; 37 38 import static org.junit.Assume.assumeTrue; 39 40 import android.app.assist.AssistStructure; 41 import android.app.assist.AssistStructure.ViewNode; 42 import android.autofillservice.cts.CannedFillResponse.CannedDataset; 43 import android.autofillservice.cts.InstrumentedAutoFillService.FillRequest; 44 import android.autofillservice.cts.InstrumentedAutoFillService.SaveRequest; 45 import android.autofillservice.cts.SimpleSaveActivity.FillExpectation; 46 import android.content.Intent; 47 import android.graphics.Bitmap; 48 import android.os.Bundle; 49 import android.platform.test.annotations.AppModeFull; 50 import android.service.autofill.BatchUpdates; 51 import android.service.autofill.CustomDescription; 52 import android.service.autofill.FillContext; 53 import android.service.autofill.FillEventHistory; 54 import android.service.autofill.RegexValidator; 55 import android.service.autofill.SaveInfo; 56 import android.service.autofill.TextValueSanitizer; 57 import android.service.autofill.Validator; 58 import android.support.test.uiautomator.By; 59 import android.support.test.uiautomator.UiObject2; 60 import android.view.View; 61 import android.view.autofill.AutofillId; 62 import android.widget.RemoteViews; 63 64 import org.junit.Test; 65 import org.junit.rules.RuleChain; 66 import org.junit.rules.TestRule; 67 68 import java.util.regex.Pattern; 69 70 public class SimpleSaveActivityTest extends CustomDescriptionWithLinkTestCase<SimpleSaveActivity> { 71 72 private static final AutofillActivityTestRule<SimpleSaveActivity> sActivityRule = 73 new AutofillActivityTestRule<SimpleSaveActivity>(SimpleSaveActivity.class, false); 74 75 private static final AutofillActivityTestRule<WelcomeActivity> sWelcomeActivityRule = 76 new AutofillActivityTestRule<WelcomeActivity>(WelcomeActivity.class, false); 77 78 public SimpleSaveActivityTest() { 79 super(SimpleSaveActivity.class); 80 } 81 82 @Override 83 protected AutofillActivityTestRule<SimpleSaveActivity> getActivityRule() { 84 return sActivityRule; 85 } 86 87 @Override 88 protected TestRule getMainTestRule() { 89 return RuleChain.outerRule(sActivityRule).around(sWelcomeActivityRule); 90 } 91 92 private void restartActivity() { 93 final Intent intent = new Intent(mContext.getApplicationContext(), 94 SimpleSaveActivity.class); 95 intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); 96 mActivity.startActivity(intent); 97 } 98 99 @Test 100 public void testAutoFillOneDatasetAndSave() throws Exception { 101 startActivity(); 102 103 // Set service. 104 enableService(); 105 106 // Set expectations. 107 sReplier.addResponse(new CannedFillResponse.Builder() 108 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT, ID_PASSWORD) 109 .addDataset(new CannedDataset.Builder() 110 .setField(ID_INPUT, "id") 111 .setField(ID_PASSWORD, "pass") 112 .setPresentation(createPresentation("YO")) 113 .build()) 114 .build()); 115 116 // Trigger autofill. 117 mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus()); 118 sReplier.getNextFillRequest(); 119 120 // Select dataset. 121 final FillExpectation autofillExpecation = mActivity.expectAutoFill("id", "pass"); 122 mUiBot.selectDataset("YO"); 123 autofillExpecation.assertAutoFilled(); 124 125 mActivity.syncRunOnUiThread(() -> { 126 mActivity.mInput.setText("ID"); 127 mActivity.mPassword.setText("PASS"); 128 mActivity.mCommit.performClick(); 129 }); 130 final UiObject2 saveUi = mUiBot.assertUpdateShowing(SAVE_DATA_TYPE_GENERIC); 131 132 // Save it... 133 mUiBot.saveForAutofill(saveUi, true); 134 135 // ... and assert results 136 final SaveRequest saveRequest = sReplier.getNextSaveRequest(); 137 assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "ID"); 138 assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_PASSWORD), "PASS"); 139 } 140 141 @Test 142 @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough") 143 public void testAutoFillOneDatasetAndSave_largeAssistStructure() throws Exception { 144 startActivity(); 145 146 mActivity.syncRunOnUiThread( 147 () -> mActivity.mInput.setAutofillHints(LARGE_STRING, LARGE_STRING, LARGE_STRING)); 148 149 // Set service. 150 enableService(); 151 152 // Set expectations. 153 sReplier.addResponse(new CannedFillResponse.Builder() 154 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT, ID_PASSWORD) 155 .addDataset(new CannedDataset.Builder() 156 .setField(ID_INPUT, "id") 157 .setField(ID_PASSWORD, "pass") 158 .setPresentation(createPresentation("YO")) 159 .build()) 160 .build()); 161 162 // Trigger autofill. 163 mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus()); 164 final FillRequest fillRequest = sReplier.getNextFillRequest(); 165 final ViewNode inputOnFill = findNodeByResourceId(fillRequest.structure, ID_INPUT); 166 final String[] hintsOnFill = inputOnFill.getAutofillHints(); 167 // Cannot compare these large strings directly becauise it could cause ANR 168 assertThat(hintsOnFill).hasLength(3); 169 Helper.assertEqualsToLargeString(hintsOnFill[0]); 170 Helper.assertEqualsToLargeString(hintsOnFill[1]); 171 Helper.assertEqualsToLargeString(hintsOnFill[2]); 172 173 // Select dataset. 174 final FillExpectation autofillExpecation = mActivity.expectAutoFill("id", "pass"); 175 mUiBot.selectDataset("YO"); 176 autofillExpecation.assertAutoFilled(); 177 178 mActivity.syncRunOnUiThread(() -> { 179 mActivity.mInput.setText("ID"); 180 mActivity.mPassword.setText("PASS"); 181 mActivity.mCommit.performClick(); 182 }); 183 final UiObject2 saveUi = mUiBot.assertUpdateShowing(SAVE_DATA_TYPE_GENERIC); 184 185 // Save it... 186 mUiBot.saveForAutofill(saveUi, true); 187 188 // ... and assert results 189 final SaveRequest saveRequest = sReplier.getNextSaveRequest(); 190 final ViewNode inputOnSave = findNodeByResourceId(saveRequest.structure, ID_INPUT); 191 assertTextAndValue(inputOnSave, "ID"); 192 assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_PASSWORD), "PASS"); 193 194 final String[] hintsOnSave = inputOnSave.getAutofillHints(); 195 // Cannot compare these large strings directly becauise it could cause ANR 196 assertThat(hintsOnSave).hasLength(3); 197 Helper.assertEqualsToLargeString(hintsOnSave[0]); 198 Helper.assertEqualsToLargeString(hintsOnSave[1]); 199 Helper.assertEqualsToLargeString(hintsOnSave[2]); 200 } 201 202 /** 203 * Simple test that only uses UiAutomator to interact with the activity, so it indirectly 204 * tests the integration of Autofill with Accessibility. 205 */ 206 @Test 207 public void testAutoFillOneDatasetAndSave_usingUiAutomatorOnly() throws Exception { 208 startActivity(); 209 210 // Set service. 211 enableService(); 212 213 // Set expectations. 214 sReplier.addResponse(new CannedFillResponse.Builder() 215 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT, ID_PASSWORD) 216 .addDataset(new CannedDataset.Builder() 217 .setField(ID_INPUT, "id") 218 .setField(ID_PASSWORD, "pass") 219 .setPresentation(createPresentation("YO")) 220 .build()) 221 .build()); 222 223 // Trigger autofill. 224 mUiBot.assertShownByRelativeId(ID_INPUT).click(); 225 sReplier.getNextFillRequest(); 226 227 // Select dataset... 228 mUiBot.selectDataset("YO"); 229 230 // ...and assert autofilled values. 231 final UiObject2 input = mUiBot.assertShownByRelativeId(ID_INPUT); 232 final UiObject2 password = mUiBot.assertShownByRelativeId(ID_PASSWORD); 233 234 assertWithMessage("wrong value for 'input'").that(input.getText()).isEqualTo("id"); 235 // TODO: password field is shown as **** ; ideally we should assert it's a password 236 // field, but UiAutomator does not exposes that info. 237 final String visiblePassword = password.getText(); 238 assertWithMessage("'password' should not be visible").that(visiblePassword) 239 .isNotEqualTo("pass"); 240 assertWithMessage("wrong value for 'password'").that(visiblePassword).hasLength(4); 241 242 // Trigger save... 243 input.setText("ID"); 244 password.setText("PASS"); 245 mUiBot.assertShownByRelativeId(ID_COMMIT).click(); 246 mUiBot.updateForAutofill(true, SAVE_DATA_TYPE_GENERIC); 247 248 // ... and assert results 249 final SaveRequest saveRequest = sReplier.getNextSaveRequest(); 250 assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "ID"); 251 assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_PASSWORD), "PASS"); 252 } 253 254 @Test 255 @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough") 256 public void testSave() throws Exception { 257 saveTest(false); 258 } 259 260 @Test 261 public void testSave_afterRotation() throws Exception { 262 assumeTrue("Rotation is supported", Helper.isRotationSupported(mContext)); 263 mUiBot.setScreenOrientation(UiBot.PORTRAIT); 264 try { 265 saveTest(true); 266 } finally { 267 try { 268 mUiBot.setScreenOrientation(UiBot.PORTRAIT); 269 cleanUpAfterScreenOrientationIsBackToPortrait(); 270 } catch (Exception e) { 271 mSafeCleanerRule.add(e); 272 } 273 } 274 } 275 276 private void saveTest(boolean rotate) throws Exception { 277 startActivity(); 278 279 // Set service. 280 enableService(); 281 282 // Set expectations. 283 sReplier.addResponse(new CannedFillResponse.Builder() 284 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT) 285 .build()); 286 287 // Trigger autofill. 288 mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus()); 289 sReplier.getNextFillRequest(); 290 291 // Trigger save. 292 mActivity.syncRunOnUiThread(() -> { 293 mActivity.mInput.setText("108"); 294 mActivity.mCommit.performClick(); 295 }); 296 UiObject2 saveUi = mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC); 297 298 if (rotate) { 299 // After the device rotates, the input field get focus and generate a new session. 300 sReplier.addResponse(CannedFillResponse.NO_RESPONSE); 301 302 mUiBot.setScreenOrientation(UiBot.LANDSCAPE); 303 saveUi = mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC); 304 } 305 306 // Save it... 307 mUiBot.saveForAutofill(saveUi, true); 308 309 // ... and assert results 310 final SaveRequest saveRequest = sReplier.getNextSaveRequest(); 311 assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "108"); 312 } 313 314 /** 315 * Emulates an app dyanmically adding the password field after username is typed. 316 */ 317 @Test 318 @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough") 319 public void testPartitionedSave() throws Exception { 320 startActivity(); 321 322 // Set service. 323 enableService(); 324 325 // 1st request 326 327 // Set expectations. 328 sReplier.addResponse(new CannedFillResponse.Builder() 329 .setRequiredSavableIds(SAVE_DATA_TYPE_USERNAME, ID_INPUT) 330 .build()); 331 332 // Trigger autofill. 333 mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus()); 334 sReplier.getNextFillRequest(); 335 336 // Set 1st field but don't commit session 337 mActivity.syncRunOnUiThread(() -> mActivity.mInput.setText("108")); 338 mUiBot.assertSaveNotShowing(); 339 340 // 2nd request 341 342 // Set expectations. 343 sReplier.addResponse(new CannedFillResponse.Builder() 344 .setRequiredSavableIds(SAVE_DATA_TYPE_USERNAME | SAVE_DATA_TYPE_PASSWORD, 345 ID_INPUT, ID_PASSWORD) 346 .build()); 347 348 // Trigger autofill. 349 mActivity.syncRunOnUiThread(() -> mActivity.mPassword.requestFocus()); 350 sReplier.getNextFillRequest(); 351 352 // Trigger save. 353 mActivity.syncRunOnUiThread(() -> { 354 mActivity.mPassword.setText("42"); 355 mActivity.mCommit.performClick(); 356 }); 357 final UiObject2 saveUi = mUiBot.assertSaveShowing(null, SAVE_DATA_TYPE_USERNAME, 358 SAVE_DATA_TYPE_PASSWORD); 359 360 // Save it... 361 mUiBot.saveForAutofill(saveUi, true); 362 363 // ... and assert results 364 final SaveRequest saveRequest = sReplier.getNextSaveRequest(); 365 assertThat(saveRequest.contexts.size()).isEqualTo(2); 366 367 assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "108"); 368 assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_PASSWORD), "42"); 369 } 370 371 /** 372 * Emulates an app using fragments to display username and password in 2 steps. 373 */ 374 @Test 375 @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough") 376 public void testDelayedSave() throws Exception { 377 startActivity(); 378 379 // Set service. 380 enableService(); 381 382 // 1st fragment. 383 384 // Set expectations. 385 sReplier.addResponse(new CannedFillResponse.Builder() 386 .setSaveInfoFlags(SaveInfo.FLAG_DELAY_SAVE).build()); 387 388 // Trigger autofill. 389 mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus()); 390 sReplier.getNextFillRequest(); 391 392 // Trigger delayed save. 393 mActivity.syncRunOnUiThread(() -> { 394 mActivity.mInput.setText("108"); 395 mActivity.mCommit.performClick(); 396 }); 397 mUiBot.assertSaveNotShowing(); 398 399 // 2nd fragment. 400 401 // Set expectations. 402 sReplier.addResponse(new CannedFillResponse.Builder() 403 // Must explicitly set visitor, otherwise setRequiredSavableIds() would get the 404 // id from the 1st context 405 .setVisitor((contexts, builder) -> { 406 final AutofillId passwordId = 407 findAutofillIdByResourceId(contexts.get(1), ID_PASSWORD); 408 final AutofillId inputId = 409 findAutofillIdByResourceId(contexts.get(0), ID_INPUT); 410 builder.setSaveInfo(new SaveInfo.Builder( 411 SAVE_DATA_TYPE_USERNAME | SAVE_DATA_TYPE_PASSWORD, 412 new AutofillId[] {inputId, passwordId}) 413 .build()); 414 }) 415 .build()); 416 417 // Trigger autofill on second "fragment" 418 mActivity.syncRunOnUiThread(() -> mActivity.mPassword.requestFocus()); 419 sReplier.getNextFillRequest(); 420 421 // Trigger delayed save. 422 mActivity.syncRunOnUiThread(() -> { 423 mActivity.mPassword.setText("42"); 424 mActivity.mCommit.performClick(); 425 }); 426 427 // Save it... 428 mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_USERNAME, SAVE_DATA_TYPE_PASSWORD); 429 430 // ... and assert results 431 final SaveRequest saveRequest = sReplier.getNextSaveRequest(); 432 assertThat(saveRequest.contexts.size()).isEqualTo(2); 433 434 // Get username from 1st request. 435 final AssistStructure structure1 = saveRequest.contexts.get(0).getStructure(); 436 assertTextAndValue(findNodeByResourceId(structure1, ID_INPUT), "108"); 437 438 // Get password from 2nd request. 439 final AssistStructure structure2 = saveRequest.contexts.get(1).getStructure(); 440 assertTextAndValue(findNodeByResourceId(structure2, ID_INPUT), "108"); 441 assertTextAndValue(findNodeByResourceId(structure2, ID_PASSWORD), "42"); 442 } 443 444 @Test 445 public void testSave_launchIntent() throws Exception { 446 startActivity(); 447 448 // Set service. 449 enableService(); 450 451 // Set expectations. 452 sReplier.setOnSave(WelcomeActivity.createSender(mContext, "Saved by the bell")) 453 .addResponse(new CannedFillResponse.Builder() 454 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT) 455 .build()); 456 457 // Trigger autofill. 458 mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus()); 459 sReplier.getNextFillRequest(); 460 461 // Trigger save. 462 mActivity.syncRunOnUiThread(() -> { 463 mActivity.mInput.setText("108"); 464 mActivity.mCommit.performClick(); 465 466 // Disable autofill so it's not triggered again after WelcomeActivity finishes 467 // and mActivity is resumed (with focus on mInput) after the session is closed 468 mActivity.mInput.setImportantForAutofill(View.IMPORTANT_FOR_AUTOFILL_NO); 469 }); 470 471 // Save it... 472 mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC); 473 sReplier.getNextSaveRequest(); 474 475 // ... and assert activity was launched 476 WelcomeActivity.assertShowing(mUiBot, "Saved by the bell"); 477 } 478 479 @Test 480 public void testSaveThenStartNewSessionRightAway() throws Exception { 481 startActivity(); 482 483 // Set service. 484 enableService(); 485 486 // Set expectations. 487 sReplier.addResponse(new CannedFillResponse.Builder() 488 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT) 489 .build()); 490 491 // Trigger autofill. 492 mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus()); 493 sReplier.getNextFillRequest(); 494 495 // Trigger save. 496 mActivity.syncRunOnUiThread(() -> { 497 mActivity.mInput.setText("108"); 498 mActivity.mCommit.performClick(); 499 }); 500 501 // Make sure Save UI for 1st session was shown.... 502 mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC); 503 504 // ...then start the new session right away (without finishing the activity). 505 sReplier.addResponse(CannedFillResponse.NO_RESPONSE); 506 mActivity.syncRunOnUiThread( 507 () -> mActivity.getAutofillManager().requestAutofill(mActivity.mInput)); 508 sReplier.getNextFillRequest(); 509 510 // Make sure Save UI for 1st session was canceled. 511 mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC); 512 } 513 514 @Test 515 public void testSaveWithParcelableOnClientState() throws Exception { 516 startActivity(); 517 518 // Set service. 519 enableService(); 520 521 // Set expectations. 522 final AutofillId id = new AutofillId(42); 523 final Bundle clientState = new Bundle(); 524 clientState.putParcelable("id", id); 525 clientState.putParcelable("my_id", new MyAutofillId(id)); 526 sReplier.addResponse(new CannedFillResponse.Builder() 527 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT) 528 .setExtras(clientState) 529 .build()); 530 531 // Trigger autofill. 532 mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus()); 533 sReplier.getNextFillRequest(); 534 535 // Trigger save. 536 mActivity.syncRunOnUiThread(() -> { 537 mActivity.mInput.setText("108"); 538 mActivity.mCommit.performClick(); 539 }); 540 UiObject2 saveUi = mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC); 541 542 // Save it... 543 mUiBot.saveForAutofill(saveUi, true); 544 545 // ... and assert results 546 final SaveRequest saveRequest = sReplier.getNextSaveRequest(); 547 assertMyClientState(saveRequest.data); 548 549 // Also check fillevent history 550 final FillEventHistory history = InstrumentedAutoFillService.getFillEventHistory(1); 551 @SuppressWarnings("deprecation") 552 final Bundle deprecatedState = history.getClientState(); 553 assertMyClientState(deprecatedState); 554 assertMyClientState(history.getEvents().get(0).getClientState()); 555 } 556 557 private void assertMyClientState(Bundle data) { 558 // Must set proper classpath before reading the data, otherwise Bundle will use it's 559 // on class classloader, which is the framework's. 560 data.setClassLoader(getClass().getClassLoader()); 561 562 final AutofillId expectedId = new AutofillId(42); 563 final AutofillId actualId = data.getParcelable("id"); 564 assertThat(actualId).isEqualTo(expectedId); 565 final MyAutofillId actualMyId = data.getParcelable("my_id"); 566 assertThat(actualMyId).isEqualTo(new MyAutofillId(expectedId)); 567 } 568 569 @Test 570 public void testCancelPreventsSaveUiFromShowing() throws Exception { 571 startActivity(); 572 573 // Set service. 574 enableService(); 575 576 // Set expectations. 577 sReplier.addResponse(new CannedFillResponse.Builder() 578 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT) 579 .build()); 580 581 // Trigger autofill. 582 mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus()); 583 sReplier.getNextFillRequest(); 584 585 // Cancel session. 586 mActivity.getAutofillManager().cancel(); 587 588 // Trigger save. 589 mActivity.syncRunOnUiThread(() -> { 590 mActivity.mInput.setText("108"); 591 mActivity.mCommit.performClick(); 592 }); 593 594 // Assert it's not showing. 595 mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC); 596 } 597 598 @Test 599 public void testDismissSave_byTappingBack() throws Exception { 600 startActivity(); 601 dismissSaveTest(DismissType.BACK_BUTTON); 602 } 603 604 @Test 605 public void testDismissSave_byTappingHome() throws Exception { 606 startActivity(); 607 dismissSaveTest(DismissType.HOME_BUTTON); 608 } 609 610 @Test 611 public void testDismissSave_byTouchingOutside() throws Exception { 612 startActivity(); 613 dismissSaveTest(DismissType.TOUCH_OUTSIDE); 614 } 615 616 @Test 617 public void testDismissSave_byFocusingOutside() throws Exception { 618 startActivity(); 619 dismissSaveTest(DismissType.FOCUS_OUTSIDE); 620 } 621 622 private void dismissSaveTest(DismissType dismissType) throws Exception { 623 // Set service. 624 enableService(); 625 626 // Set expectations. 627 sReplier.addResponse(new CannedFillResponse.Builder() 628 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT) 629 .build()); 630 631 // Trigger autofill. 632 mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus()); 633 sReplier.getNextFillRequest(); 634 635 // Trigger save. 636 mActivity.syncRunOnUiThread(() -> { 637 mActivity.mInput.setText("108"); 638 mActivity.mCommit.performClick(); 639 }); 640 mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC); 641 642 // Then make sure it goes away when user doesn't want it.. 643 switch (dismissType) { 644 case BACK_BUTTON: 645 mUiBot.pressBack(); 646 break; 647 case HOME_BUTTON: 648 mUiBot.pressHome(); 649 break; 650 case TOUCH_OUTSIDE: 651 mUiBot.assertShownByText(TEXT_LABEL).click(); 652 break; 653 case FOCUS_OUTSIDE: 654 mActivity.syncRunOnUiThread(() -> mActivity.mLabel.requestFocus()); 655 mUiBot.assertShownByText(TEXT_LABEL).click(); 656 break; 657 default: 658 throw new IllegalArgumentException("invalid dismiss type: " + dismissType); 659 } 660 mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC); 661 } 662 663 @Test 664 public void testTapHomeWhileDatasetPickerUiIsShowing() throws Exception { 665 startActivity(); 666 enableService(); 667 final MyAutofillCallback callback = mActivity.registerCallback(); 668 669 // Set expectations. 670 sReplier.addResponse(new CannedFillResponse.Builder() 671 .addDataset(new CannedDataset.Builder() 672 .setField(ID_INPUT, "id") 673 .setField(ID_PASSWORD, "pass") 674 .setPresentation(createPresentation("YO")) 675 .build()) 676 .build()); 677 678 // Trigger autofill. 679 mUiBot.assertShownByRelativeId(ID_INPUT).click(); 680 sReplier.getNextFillRequest(); 681 mUiBot.assertDatasets("YO"); 682 callback.assertUiShownEvent(mActivity.mInput); 683 684 // Go home, you are drunk! 685 mUiBot.pressHome(); 686 mUiBot.assertNoDatasets(); 687 callback.assertUiHiddenEvent(mActivity.mInput); 688 689 // Switch back to the activity. 690 restartActivity(); 691 mUiBot.assertShownByText(TEXT_LABEL, Timeouts.ACTIVITY_RESURRECTION); 692 final UiObject2 datasetPicker = mUiBot.assertDatasets("YO"); 693 callback.assertUiShownEvent(mActivity.mInput); 694 695 // Now autofill it. 696 final FillExpectation autofillExpecation = mActivity.expectAutoFill("id", "pass"); 697 mUiBot.selectDataset(datasetPicker, "YO"); 698 autofillExpecation.assertAutoFilled(); 699 } 700 701 @Test 702 public void testTapHomeWhileSaveUiIsShowing() throws Exception { 703 startActivity(); 704 enableService(); 705 706 // Set expectations. 707 sReplier.addResponse(new CannedFillResponse.Builder() 708 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT) 709 .build()); 710 711 // Trigger autofill. 712 mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus()); 713 sReplier.getNextFillRequest(); 714 mUiBot.assertNoDatasetsEver(); 715 716 // Trigger save, but don't tap it. 717 mActivity.syncRunOnUiThread(() -> { 718 mActivity.mInput.setText("108"); 719 mActivity.mCommit.performClick(); 720 }); 721 mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC); 722 723 // Go home, you are drunk! 724 mUiBot.pressHome(); 725 mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC); 726 727 // Prepare the response for the next session, which will be automatically triggered 728 // when the activity is brought back. 729 sReplier.addResponse(new CannedFillResponse.Builder() 730 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT, ID_PASSWORD) 731 .addDataset(new CannedDataset.Builder() 732 .setField(ID_INPUT, "id") 733 .setField(ID_PASSWORD, "pass") 734 .setPresentation(createPresentation("YO")) 735 .build()) 736 .build()); 737 738 // Switch back to the activity. 739 restartActivity(); 740 mUiBot.assertShownByText(TEXT_LABEL, Timeouts.ACTIVITY_RESURRECTION); 741 mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC); 742 sReplier.getNextFillRequest(); 743 mUiBot.assertNoDatasetsEver(); 744 745 // Trigger and select UI. 746 mActivity.syncRunOnUiThread(() -> mActivity.mPassword.requestFocus()); 747 final FillExpectation autofillExpecation = mActivity.expectAutoFill("id", "pass"); 748 mUiBot.selectDataset("YO"); 749 750 // Assert it. 751 autofillExpecation.assertAutoFilled(); 752 } 753 754 @Override 755 protected void saveUiRestoredAfterTappingLinkTest(PostSaveLinkTappedAction type) 756 throws Exception { 757 startActivity(); 758 // Set service. 759 enableService(); 760 761 // Set expectations. 762 sReplier.addResponse(new CannedFillResponse.Builder() 763 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT) 764 .setSaveInfoVisitor((contexts, builder) -> builder 765 .setCustomDescription(newCustomDescription(WelcomeActivity.class))) 766 .build()); 767 768 // Trigger autofill. 769 mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus()); 770 sReplier.getNextFillRequest(); 771 772 // Trigger save. 773 mActivity.syncRunOnUiThread(() -> { 774 mActivity.mInput.setText("108"); 775 mActivity.mCommit.performClick(); 776 }); 777 final UiObject2 saveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_GENERIC); 778 779 // Tap the link. 780 tapSaveUiLink(saveUi); 781 782 // Make sure new activity is shown... 783 WelcomeActivity.assertShowingDefaultMessage(mUiBot); 784 mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC); 785 786 // .. then do something to return to previous activity... 787 switch (type) { 788 case ROTATE_THEN_TAP_BACK_BUTTON: 789 // After the device rotates, the input field get focus and generate a new session. 790 sReplier.addResponse(CannedFillResponse.NO_RESPONSE); 791 792 mUiBot.setScreenOrientation(UiBot.LANDSCAPE); 793 WelcomeActivity.assertShowingDefaultMessage(mUiBot); 794 // not breaking on purpose 795 case TAP_BACK_BUTTON: 796 // ..then go back and save it. 797 mUiBot.pressBack(); 798 break; 799 case FINISH_ACTIVITY: 800 // ..then finishes it. 801 WelcomeActivity.finishIt(); 802 break; 803 default: 804 throw new IllegalArgumentException("invalid type: " + type); 805 } 806 // Make sure previous activity is back... 807 mUiBot.assertShownByRelativeId(ID_INPUT); 808 809 // ... and tap save. 810 final UiObject2 newSaveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_GENERIC); 811 mUiBot.saveForAutofill(newSaveUi, true); 812 813 final SaveRequest saveRequest = sReplier.getNextSaveRequest(); 814 assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "108"); 815 816 } 817 818 @Override 819 protected void cleanUpAfterScreenOrientationIsBackToPortrait() throws Exception { 820 sReplier.getNextFillRequest(); 821 } 822 823 @Override 824 protected void tapLinkThenTapBackThenStartOverTest(PostSaveLinkTappedAction action, 825 boolean manualRequest) throws Exception { 826 startActivity(); 827 // Set service. 828 enableService(); 829 830 // Set expectations. 831 sReplier.addResponse(new CannedFillResponse.Builder() 832 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT) 833 .setSaveInfoVisitor((contexts, builder) -> builder 834 .setCustomDescription(newCustomDescription(WelcomeActivity.class))) 835 .build()); 836 837 // Trigger autofill. 838 mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus()); 839 sReplier.getNextFillRequest(); 840 841 // Trigger save. 842 mActivity.syncRunOnUiThread(() -> { 843 mActivity.mInput.setText("108"); 844 mActivity.mCommit.performClick(); 845 }); 846 final UiObject2 saveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_GENERIC); 847 848 // Tap the link. 849 tapSaveUiLink(saveUi); 850 851 // Make sure new activity is shown. 852 WelcomeActivity.assertShowingDefaultMessage(mUiBot); 853 mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC); 854 855 // Tap back to restore the Save UI... 856 mUiBot.pressBack(); 857 // Make sure previous activity is back... 858 mUiBot.assertShownByRelativeId(ID_LABEL); 859 860 // ...but don't tap it... 861 final UiObject2 saveUi2 = mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC); 862 863 // ...instead, do something to dismiss it: 864 switch (action) { 865 case TOUCH_OUTSIDE: 866 mUiBot.assertShownByRelativeId(ID_LABEL).longClick(); 867 break; 868 case TAP_NO_ON_SAVE_UI: 869 mUiBot.saveForAutofill(saveUi2, false); 870 break; 871 case TAP_YES_ON_SAVE_UI: 872 mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC); 873 final SaveRequest saveRequest = sReplier.getNextSaveRequest(); 874 assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "108"); 875 break; 876 default: 877 throw new IllegalArgumentException("invalid action: " + action); 878 } 879 mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC); 880 881 // Now triggers a new session and do business as usual... 882 sReplier.addResponse(new CannedFillResponse.Builder() 883 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT) 884 .build()); 885 886 // Trigger autofill. 887 if (manualRequest) { 888 mActivity.getAutofillManager().requestAutofill(mActivity.mInput); 889 } else { 890 mActivity.syncRunOnUiThread(() -> mActivity.mPassword.requestFocus()); 891 } 892 893 sReplier.getNextFillRequest(); 894 895 // Trigger save. 896 mActivity.syncRunOnUiThread(() -> { 897 mActivity.mInput.setText("42"); 898 mActivity.mCommit.performClick(); 899 }); 900 901 // Save it... 902 mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC); 903 904 // ... and assert results 905 final SaveRequest saveRequest = sReplier.getNextSaveRequest(); 906 assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "42"); 907 } 908 909 @Override 910 protected void saveUiCancelledAfterTappingLinkTest(PostSaveLinkTappedAction type) 911 throws Exception { 912 startActivity(false); 913 // Set service. 914 enableService(); 915 916 // Set expectations. 917 sReplier.addResponse(new CannedFillResponse.Builder() 918 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT) 919 .setSaveInfoVisitor((contexts, builder) -> builder 920 .setCustomDescription(newCustomDescription(WelcomeActivity.class))) 921 .build()); 922 923 // Trigger autofill. 924 mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus()); 925 sReplier.getNextFillRequest(); 926 927 // Trigger save. 928 mActivity.syncRunOnUiThread(() -> { 929 mActivity.mInput.setText("108"); 930 mActivity.mCommit.performClick(); 931 }); 932 final UiObject2 saveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_GENERIC); 933 934 // Tap the link. 935 tapSaveUiLink(saveUi); 936 // Make sure new activity is shown... 937 WelcomeActivity.assertShowingDefaultMessage(mUiBot); 938 mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC); 939 940 switch (type) { 941 case LAUNCH_PREVIOUS_ACTIVITY: 942 startActivityOnNewTask(SimpleSaveActivity.class); 943 break; 944 case LAUNCH_NEW_ACTIVITY: 945 // Launch a 3rd activity... 946 startActivityOnNewTask(LoginActivity.class); 947 mUiBot.assertShownByRelativeId(ID_USERNAME_CONTAINER); 948 // ...then go back 949 mUiBot.pressBack(); 950 break; 951 default: 952 throw new IllegalArgumentException("invalid type: " + type); 953 } 954 // Make sure right activity is showing 955 mUiBot.assertShownByRelativeId(ID_INPUT, Timeouts.ACTIVITY_RESURRECTION); 956 957 mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC); 958 } 959 960 @Test 961 @AppModeFull(reason = "Service-specific test") 962 public void testSelectedDatasetsAreSentOnSaveRequest() throws Exception { 963 startActivity(); 964 965 // Set service. 966 enableService(); 967 968 // Set expectations. 969 sReplier.addResponse(new CannedFillResponse.Builder() 970 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT, ID_PASSWORD) 971 // Added on reversed order on purpose 972 .addDataset(new CannedDataset.Builder() 973 .setId("D2") 974 .setField(ID_INPUT, "id again") 975 .setField(ID_PASSWORD, "pass") 976 .setPresentation(createPresentation("D2")) 977 .build()) 978 .addDataset(new CannedDataset.Builder() 979 .setId("D1") 980 .setField(ID_INPUT, "id") 981 .setPresentation(createPresentation("D1")) 982 .build()) 983 .build()); 984 985 // Trigger autofill. 986 mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus()); 987 sReplier.getNextFillRequest(); 988 989 // Select 1st dataset. 990 final FillExpectation autofillExpecation1 = mActivity.expectAutoFill("id"); 991 final UiObject2 picker1 = mUiBot.assertDatasets("D2", "D1"); 992 mUiBot.selectDataset(picker1, "D1"); 993 autofillExpecation1.assertAutoFilled(); 994 995 // Select 2nd dataset. 996 mActivity.syncRunOnUiThread(() -> mActivity.mPassword.requestFocus()); 997 final FillExpectation autofillExpecation2 = mActivity.expectAutoFill("id again", "pass"); 998 final UiObject2 picker2 = mUiBot.assertDatasets("D2"); 999 mUiBot.selectDataset(picker2, "D2"); 1000 autofillExpecation2.assertAutoFilled(); 1001 1002 mActivity.syncRunOnUiThread(() -> { 1003 mActivity.mInput.setText("ID"); 1004 mActivity.mPassword.setText("PASS"); 1005 mActivity.mCommit.performClick(); 1006 }); 1007 final UiObject2 saveUi = mUiBot.assertUpdateShowing(SAVE_DATA_TYPE_GENERIC); 1008 1009 // Save it... 1010 mUiBot.saveForAutofill(saveUi, true); 1011 1012 // ... and assert results 1013 final SaveRequest saveRequest = sReplier.getNextSaveRequest(); 1014 assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "ID"); 1015 assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_PASSWORD), "PASS"); 1016 assertThat(saveRequest.datasetIds).containsExactly("D1", "D2").inOrder(); 1017 } 1018 1019 @Override 1020 protected void tapLinkLaunchTrampolineActivityThenTapBackAndStartNewSessionTest() 1021 throws Exception { 1022 // Prepare activity. 1023 startActivity(); 1024 mActivity.mInput.getRootView() 1025 .setImportantForAutofill(View.IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS); 1026 1027 // Set service. 1028 enableService(); 1029 1030 // Set expectations. 1031 sReplier.addResponse(new CannedFillResponse.Builder() 1032 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT) 1033 .setSaveInfoVisitor((contexts, builder) -> builder 1034 .setCustomDescription( 1035 newCustomDescription(TrampolineWelcomeActivity.class))) 1036 .build()); 1037 1038 // Trigger autofill. 1039 mActivity.getAutofillManager().requestAutofill(mActivity.mInput); 1040 sReplier.getNextFillRequest(); 1041 1042 // Trigger save. 1043 mActivity.syncRunOnUiThread(() -> { 1044 mActivity.mInput.setText("108"); 1045 mActivity.mCommit.performClick(); 1046 }); 1047 final UiObject2 saveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_GENERIC); 1048 1049 // Tap the link. 1050 tapSaveUiLink(saveUi); 1051 1052 // Make sure new activity is shown... 1053 WelcomeActivity.assertShowingDefaultMessage(mUiBot); 1054 1055 // Save UI should be showing as well, since Trampoline finished. 1056 mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC); 1057 1058 // Dismiss Save Dialog 1059 mUiBot.pressBack(); 1060 // Go back and make sure it's showing the right activity. 1061 mUiBot.pressBack(); 1062 mUiBot.assertShownByRelativeId(ID_LABEL); 1063 1064 // Now start a new session. 1065 sReplier.addResponse(new CannedFillResponse.Builder() 1066 .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_PASSWORD) 1067 .build()); 1068 mActivity.getAutofillManager().requestAutofill(mActivity.mPassword); 1069 sReplier.getNextFillRequest(); 1070 mActivity.syncRunOnUiThread(() -> { 1071 mActivity.mPassword.setText("42"); 1072 mActivity.mCommit.performClick(); 1073 }); 1074 mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD); 1075 final SaveRequest saveRequest = sReplier.getNextSaveRequest(); 1076 assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "108"); 1077 assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_PASSWORD), "42"); 1078 } 1079 1080 @Test 1081 public void testSanitizeOnSaveWhenAppChangeValues() throws Exception { 1082 startActivity(); 1083 1084 // Set listeners that will change the saved value 1085 new AntiTrimmerTextWatcher(mActivity.mInput); 1086 new AntiTrimmerTextWatcher(mActivity.mPassword); 1087 1088 // Set service. 1089 enableService(); 1090 1091 // Set expectations. 1092 sReplier.addResponse(new CannedFillResponse.Builder() 1093 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT) 1094 .setSaveInfoVisitor((contexts, builder) -> { 1095 final FillContext context = contexts.get(0); 1096 final AutofillId inputId = findAutofillIdByResourceId(context, ID_INPUT); 1097 final AutofillId passwordId = findAutofillIdByResourceId(context, ID_PASSWORD); 1098 builder.addSanitizer(new TextValueSanitizer(TRIMMER_PATTERN, "$1"), inputId, 1099 passwordId); 1100 }) 1101 .build()); 1102 1103 // Trigger autofill. 1104 mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus()); 1105 sReplier.getNextFillRequest(); 1106 1107 // Trigger save. 1108 mActivity.syncRunOnUiThread(() -> { 1109 mActivity.mInput.setText("id"); 1110 mActivity.mPassword.setText("pass"); 1111 mActivity.mCommit.performClick(); 1112 }); 1113 1114 // Save it... 1115 mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC); 1116 1117 // ... and assert results 1118 final SaveRequest saveRequest = sReplier.getNextSaveRequest(); 1119 assertTextValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "id"); 1120 assertTextValue(findNodeByResourceId(saveRequest.structure, ID_PASSWORD), "pass"); 1121 } 1122 1123 @Test 1124 @AppModeFull(reason = "testSanitizeOnSaveWhenAppChangeValues() is enough") 1125 public void testSanitizeOnSaveNoChange() throws Exception { 1126 startActivity(); 1127 1128 // Set service. 1129 enableService(); 1130 1131 // Set expectations. 1132 sReplier.addResponse(new CannedFillResponse.Builder() 1133 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT) 1134 .setOptionalSavableIds(ID_PASSWORD) 1135 .setSaveInfoVisitor((contexts, builder) -> { 1136 final FillContext context = contexts.get(0); 1137 final AutofillId inputId = findAutofillIdByResourceId(context, ID_INPUT); 1138 final AutofillId passwordId = findAutofillIdByResourceId(context, ID_PASSWORD); 1139 builder.addSanitizer(new TextValueSanitizer(TRIMMER_PATTERN, "$1"), inputId, 1140 passwordId); 1141 }) 1142 .build()); 1143 1144 // Trigger autofill. 1145 mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus()); 1146 sReplier.getNextFillRequest(); 1147 mUiBot.assertNoDatasetsEver(); 1148 1149 // Trigger save. 1150 mActivity.syncRunOnUiThread(() -> { 1151 mActivity.mInput.setText("#id#"); 1152 mActivity.mPassword.setText("#pass#"); 1153 mActivity.mCommit.performClick(); 1154 }); 1155 1156 // Save it... 1157 mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC); 1158 1159 // ... and assert results 1160 final SaveRequest saveRequest = sReplier.getNextSaveRequest(); 1161 assertTextValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "id"); 1162 assertTextValue(findNodeByResourceId(saveRequest.structure, ID_PASSWORD), "pass"); 1163 } 1164 1165 @Test 1166 @AppModeFull(reason = "testSanitizeOnSaveWhenAppChangeValues() is enough") 1167 public void testDontSaveWhenSanitizedValueForRequiredFieldDidntChange() throws Exception { 1168 startActivity(); 1169 1170 // Set listeners that will change the saved value 1171 new AntiTrimmerTextWatcher(mActivity.mInput); 1172 new AntiTrimmerTextWatcher(mActivity.mPassword); 1173 1174 // Set service. 1175 enableService(); 1176 1177 // Set expectations. 1178 sReplier.addResponse(new CannedFillResponse.Builder() 1179 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT, ID_PASSWORD) 1180 .setSaveInfoVisitor((contexts, builder) -> { 1181 final FillContext context = contexts.get(0); 1182 final AutofillId inputId = findAutofillIdByResourceId(context, ID_INPUT); 1183 final AutofillId passwordId = findAutofillIdByResourceId(context, ID_PASSWORD); 1184 builder.addSanitizer(new TextValueSanitizer(TRIMMER_PATTERN, "$1"), inputId, 1185 passwordId); 1186 }) 1187 .addDataset(new CannedDataset.Builder() 1188 .setField(ID_INPUT, "id") 1189 .setField(ID_PASSWORD, "pass") 1190 .setPresentation(createPresentation("YO")) 1191 .build()) 1192 .build()); 1193 1194 // Trigger autofill. 1195 mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus()); 1196 sReplier.getNextFillRequest(); 1197 1198 mActivity.syncRunOnUiThread(() -> { 1199 mActivity.mInput.setText("id"); 1200 mActivity.mPassword.setText("pass"); 1201 mActivity.mCommit.performClick(); 1202 }); 1203 1204 mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC); 1205 } 1206 1207 @Test 1208 @AppModeFull(reason = "testSanitizeOnSaveWhenAppChangeValues() is enough") 1209 public void testDontSaveWhenSanitizedValueForOptionalFieldDidntChange() throws Exception { 1210 startActivity(); 1211 1212 // Set service. 1213 enableService(); 1214 1215 // Set expectations. 1216 sReplier.addResponse(new CannedFillResponse.Builder() 1217 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT) 1218 .setOptionalSavableIds(ID_PASSWORD) 1219 .setSaveInfoVisitor((contexts, builder) -> { 1220 final FillContext context = contexts.get(0); 1221 final AutofillId passwordId = findAutofillIdByResourceId(context, ID_PASSWORD); 1222 builder.addSanitizer(new TextValueSanitizer(Pattern.compile("(pass) "), "$1"), 1223 passwordId); 1224 }) 1225 .addDataset(new CannedDataset.Builder() 1226 .setField(ID_INPUT, "id") 1227 .setField(ID_PASSWORD, "pass") 1228 .setPresentation(createPresentation("YO")) 1229 .build()) 1230 .build()); 1231 1232 // Trigger autofill. 1233 mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus()); 1234 sReplier.getNextFillRequest(); 1235 1236 mActivity.syncRunOnUiThread(() -> { 1237 mActivity.mInput.setText("id"); 1238 mActivity.mPassword.setText("#pass#"); 1239 mActivity.mCommit.performClick(); 1240 }); 1241 1242 mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC); 1243 } 1244 1245 @Test 1246 @AppModeFull(reason = "testSanitizeOnSaveWhenAppChangeValues() is enough") 1247 public void testDontSaveWhenRequiredFieldFailedSanitization() throws Exception { 1248 startActivity(); 1249 1250 // Set service. 1251 enableService(); 1252 1253 // Set expectations. 1254 sReplier.addResponse(new CannedFillResponse.Builder() 1255 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT, ID_PASSWORD) 1256 .setSaveInfoVisitor((contexts, builder) -> { 1257 final FillContext context = contexts.get(0); 1258 final AutofillId inputId = findAutofillIdByResourceId(context, ID_INPUT); 1259 final AutofillId passwordId = findAutofillIdByResourceId(context, ID_PASSWORD); 1260 builder.addSanitizer(new TextValueSanitizer(Pattern.compile("dude"), "$1"), 1261 inputId, passwordId); 1262 }) 1263 .addDataset(new CannedDataset.Builder() 1264 .setField(ID_INPUT, "#id#") 1265 .setField(ID_PASSWORD, "#pass#") 1266 .setPresentation(createPresentation("YO")) 1267 .build()) 1268 .build()); 1269 1270 // Trigger autofill. 1271 mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus()); 1272 sReplier.getNextFillRequest(); 1273 1274 mActivity.syncRunOnUiThread(() -> { 1275 mActivity.mInput.setText("id"); 1276 mActivity.mPassword.setText("pass"); 1277 mActivity.mCommit.performClick(); 1278 }); 1279 1280 mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC); 1281 } 1282 1283 @Test 1284 @AppModeFull(reason = "testSanitizeOnSaveWhenAppChangeValues() is enough") 1285 public void testDontSaveWhenOptionalFieldFailedSanitization() throws Exception { 1286 startActivity(); 1287 1288 // Set service. 1289 enableService(); 1290 1291 // Set expectations. 1292 sReplier.addResponse(new CannedFillResponse.Builder() 1293 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT) 1294 .setOptionalSavableIds(ID_PASSWORD) 1295 .setSaveInfoVisitor((contexts, builder) -> { 1296 final FillContext context = contexts.get(0); 1297 final AutofillId inputId = findAutofillIdByResourceId(context, ID_INPUT); 1298 final AutofillId passwordId = findAutofillIdByResourceId(context, ID_PASSWORD); 1299 builder.addSanitizer(new TextValueSanitizer(Pattern.compile("dude"), "$1"), 1300 inputId, passwordId); 1301 1302 }) 1303 .addDataset(new CannedDataset.Builder() 1304 .setField(ID_INPUT, "id") 1305 .setField(ID_PASSWORD, "#pass#") 1306 .setPresentation(createPresentation("YO")) 1307 .build()) 1308 .build()); 1309 1310 // Trigger autofill. 1311 mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus()); 1312 sReplier.getNextFillRequest(); 1313 1314 mActivity.syncRunOnUiThread(() -> { 1315 mActivity.mInput.setText("id"); 1316 mActivity.mPassword.setText("pass"); 1317 mActivity.mCommit.performClick(); 1318 }); 1319 1320 mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC); 1321 } 1322 1323 @Test 1324 @AppModeFull(reason = "testSanitizeOnSaveWhenAppChangeValues() is enough") 1325 public void testDontSaveWhenInitialValueAndNoUserInputAndServiceDatasets() throws Throwable { 1326 // Prepare activitiy. 1327 startActivity(); 1328 mActivity.syncRunOnUiThread(() -> { 1329 // NOTE: input's value must be a subset of the dataset value, otherwise the dataset 1330 // picker is filtered out 1331 mActivity.mInput.setText("f"); 1332 mActivity.mPassword.setText("b"); 1333 }); 1334 1335 // Set service. 1336 enableService(); 1337 1338 // Set expectations. 1339 sReplier.addResponse(new CannedFillResponse.Builder() 1340 .addDataset(new CannedDataset.Builder() 1341 .setField(ID_INPUT, "foo") 1342 .setField(ID_PASSWORD, "bar") 1343 .setPresentation(createPresentation("The Dude")) 1344 .build()) 1345 .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_INPUT, ID_PASSWORD).build()); 1346 1347 // Trigger auto-fill. 1348 mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus()); 1349 sReplier.getNextFillRequest(); 1350 mUiBot.assertDatasets("The Dude"); 1351 1352 // Trigger save. 1353 mActivity.getAutofillManager().commit(); 1354 1355 // Assert it's not showing. 1356 mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD); 1357 } 1358 1359 @Test 1360 public void testExplicitySaveButton() throws Exception { 1361 explicitySaveButtonTest(false, 0); 1362 } 1363 1364 @Test 1365 public void testExplicitySaveButtonWhenAppClearFields() throws Exception { 1366 explicitySaveButtonTest(true, 0); 1367 } 1368 1369 @Test 1370 public void testExplicitySaveButtonOnly() throws Exception { 1371 explicitySaveButtonTest(false, SaveInfo.FLAG_DONT_SAVE_ON_FINISH); 1372 } 1373 1374 /** 1375 * Tests scenario where service explicitly indicates which button is used to save. 1376 */ 1377 private void explicitySaveButtonTest(boolean clearFieldsOnSubmit, int flags) throws Exception { 1378 final boolean testBitmap = false; 1379 startActivity(); 1380 mActivity.setAutoCommit(false); 1381 mActivity.setClearFieldsOnSubmit(clearFieldsOnSubmit); 1382 1383 // Set service. 1384 enableService(); 1385 1386 // Set expectations. 1387 sReplier.addResponse(new CannedFillResponse.Builder() 1388 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT) 1389 .setSaveTriggerId(mActivity.mCommit.getAutofillId()) 1390 .setSaveInfoFlags(flags) 1391 .build()); 1392 1393 // Trigger autofill. 1394 mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus()); 1395 sReplier.getNextFillRequest(); 1396 1397 // Trigger save. 1398 mActivity.syncRunOnUiThread(() -> mActivity.mInput.setText("108")); 1399 1400 // Take a screenshot to make sure button doesn't disappear. 1401 final String commitBefore = mUiBot.assertShownByRelativeId(ID_COMMIT).getText(); 1402 assertThat(commitBefore.toUpperCase()).isEqualTo("COMMIT"); 1403 // Disable unnecessary screenshot tests as takeScreenshot() fails on some device. 1404 1405 final Bitmap screenshotBefore = testBitmap ? mActivity.takeScreenshot(mActivity.mCommit) 1406 : null; 1407 1408 // Save it... 1409 mActivity.syncRunOnUiThread(() -> mActivity.mCommit.performClick()); 1410 final UiObject2 saveUi = mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC); 1411 mUiBot.saveForAutofill(saveUi, true); 1412 1413 // Make sure save button is showning (it was removed on earlier versions of the feature) 1414 final String commitAfter = mUiBot.assertShownByRelativeId(ID_COMMIT).getText(); 1415 assertThat(commitAfter.toUpperCase()).isEqualTo("COMMIT"); 1416 final Bitmap screenshotAfter = testBitmap ? mActivity.takeScreenshot(mActivity.mCommit) 1417 : null; 1418 1419 // ... and assert results 1420 final SaveRequest saveRequest = sReplier.getNextSaveRequest(); 1421 assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "108"); 1422 1423 if (testBitmap) { 1424 Helper.assertBitmapsAreSame("commit-button", screenshotBefore, screenshotAfter); 1425 } 1426 } 1427 1428 @Override 1429 protected void tapLinkAfterUpdateAppliedTest(boolean updateLinkView) throws Exception { 1430 startActivity(); 1431 // Set service. 1432 enableService(); 1433 1434 // Set expectations. 1435 sReplier.addResponse(new CannedFillResponse.Builder() 1436 .setSaveInfoVisitor((contexts, builder) -> { 1437 // Set response with custom description 1438 final AutofillId id = findAutofillIdByResourceId(contexts.get(0), ID_INPUT); 1439 final CustomDescription.Builder customDescription = 1440 newCustomDescriptionBuilder(WelcomeActivity.class); 1441 final RemoteViews update = newTemplate(); 1442 if (updateLinkView) { 1443 update.setCharSequence(R.id.link, "setText", "TAP ME IF YOU CAN"); 1444 } else { 1445 update.setCharSequence(R.id.static_text, "setText", "ME!"); 1446 } 1447 Validator validCondition = new RegexValidator(id, Pattern.compile(".*")); 1448 customDescription.batchUpdate(validCondition, 1449 new BatchUpdates.Builder().updateTemplate(update).build()); 1450 1451 builder.setCustomDescription(customDescription.build()); 1452 }) 1453 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT) 1454 .build()); 1455 1456 // Trigger autofill. 1457 mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus()); 1458 sReplier.getNextFillRequest(); 1459 // Trigger save. 1460 mActivity.syncRunOnUiThread(() -> { 1461 mActivity.mInput.setText("108"); 1462 mActivity.mCommit.performClick(); 1463 }); 1464 final UiObject2 saveUi; 1465 if (updateLinkView) { 1466 saveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_GENERIC, "TAP ME IF YOU CAN"); 1467 } else { 1468 saveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_GENERIC); 1469 final UiObject2 changed = saveUi.findObject(By.res(mPackageName, ID_STATIC_TEXT)); 1470 assertThat(changed.getText()).isEqualTo("ME!"); 1471 } 1472 1473 // Tap the link. 1474 tapSaveUiLink(saveUi); 1475 1476 // Make sure new activity is shown... 1477 WelcomeActivity.assertShowingDefaultMessage(mUiBot); 1478 mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC); 1479 } 1480 } 1481