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.saveui; 17 18 import static android.autofillservice.cts.activities.LoginActivity.ID_USERNAME_CONTAINER; 19 import static android.autofillservice.cts.activities.SimpleSaveActivity.ID_COMMIT; 20 import static android.autofillservice.cts.activities.SimpleSaveActivity.ID_INPUT; 21 import static android.autofillservice.cts.activities.SimpleSaveActivity.ID_LABEL; 22 import static android.autofillservice.cts.activities.SimpleSaveActivity.ID_PASSWORD; 23 import static android.autofillservice.cts.activities.SimpleSaveActivity.TEXT_LABEL; 24 import static android.autofillservice.cts.testcore.AntiTrimmerTextWatcher.TRIMMER_PATTERN; 25 import static android.autofillservice.cts.testcore.Helper.ID_STATIC_TEXT; 26 import static android.autofillservice.cts.testcore.Helper.ID_USERNAME; 27 import static android.autofillservice.cts.testcore.Helper.LARGE_STRING; 28 import static android.autofillservice.cts.testcore.Helper.assertTextAndValue; 29 import static android.autofillservice.cts.testcore.Helper.assertTextValue; 30 import static android.autofillservice.cts.testcore.Helper.findAutofillIdByResourceId; 31 import static android.autofillservice.cts.testcore.Helper.findNodeByResourceId; 32 import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_GENERIC; 33 import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD; 34 import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_USERNAME; 35 36 import static com.google.common.truth.Truth.assertThat; 37 import static com.google.common.truth.Truth.assertWithMessage; 38 39 import static org.junit.Assume.assumeTrue; 40 41 import android.app.assist.AssistStructure; 42 import android.app.assist.AssistStructure.ViewNode; 43 import android.autofillservice.cts.R; 44 import android.autofillservice.cts.activities.LoginActivity; 45 import android.autofillservice.cts.activities.SecondActivity; 46 import android.autofillservice.cts.activities.SimpleSaveActivity; 47 import android.autofillservice.cts.activities.SimpleSaveActivity.FillExpectation; 48 import android.autofillservice.cts.activities.TrampolineWelcomeActivity; 49 import android.autofillservice.cts.activities.ViewActionActivity; 50 import android.autofillservice.cts.activities.WelcomeActivity; 51 import android.autofillservice.cts.commontests.CustomDescriptionWithLinkTestCase; 52 import android.autofillservice.cts.testcore.AntiTrimmerTextWatcher; 53 import android.autofillservice.cts.testcore.AutofillActivityTestRule; 54 import android.autofillservice.cts.testcore.CannedFillResponse; 55 import android.autofillservice.cts.testcore.CannedFillResponse.CannedDataset; 56 import android.autofillservice.cts.testcore.DismissType; 57 import android.autofillservice.cts.testcore.Helper; 58 import android.autofillservice.cts.testcore.InstrumentedAutoFillService; 59 import android.autofillservice.cts.testcore.InstrumentedAutoFillService.FillRequest; 60 import android.autofillservice.cts.testcore.InstrumentedAutoFillService.SaveRequest; 61 import android.autofillservice.cts.testcore.MyAutofillCallback; 62 import android.autofillservice.cts.testcore.MyAutofillId; 63 import android.autofillservice.cts.testcore.Timeouts; 64 import android.autofillservice.cts.testcore.UiBot; 65 import android.content.Intent; 66 import android.graphics.Bitmap; 67 import android.os.Bundle; 68 import android.platform.test.annotations.AppModeFull; 69 import android.platform.test.annotations.Presubmit; 70 import android.service.autofill.BatchUpdates; 71 import android.service.autofill.CustomDescription; 72 import android.service.autofill.FillContext; 73 import android.service.autofill.FillEventHistory; 74 import android.service.autofill.RegexValidator; 75 import android.service.autofill.SaveInfo; 76 import android.service.autofill.TextValueSanitizer; 77 import android.service.autofill.Validator; 78 import android.support.test.uiautomator.By; 79 import android.support.test.uiautomator.UiObject2; 80 import android.text.Spannable; 81 import android.text.SpannableString; 82 import android.text.style.URLSpan; 83 import android.view.View; 84 import android.view.autofill.AutofillId; 85 import android.widget.RemoteViews; 86 87 import org.junit.Test; 88 import org.junit.rules.RuleChain; 89 import org.junit.rules.TestRule; 90 91 import java.util.regex.Pattern; 92 93 public class SimpleSaveActivityTest extends CustomDescriptionWithLinkTestCase<SimpleSaveActivity> { 94 95 private static final AutofillActivityTestRule<SimpleSaveActivity> sActivityRule = 96 new AutofillActivityTestRule<SimpleSaveActivity>(SimpleSaveActivity.class, false); 97 98 private static final AutofillActivityTestRule<WelcomeActivity> sWelcomeActivityRule = 99 new AutofillActivityTestRule<WelcomeActivity>(WelcomeActivity.class, false); 100 SimpleSaveActivityTest()101 public SimpleSaveActivityTest() { 102 super(SimpleSaveActivity.class); 103 } 104 105 @Override getActivityRule()106 protected AutofillActivityTestRule<SimpleSaveActivity> getActivityRule() { 107 return sActivityRule; 108 } 109 110 @Override getMainTestRule()111 protected TestRule getMainTestRule() { 112 return RuleChain.outerRule(sActivityRule).around(sWelcomeActivityRule); 113 } 114 restartActivity()115 private void restartActivity() { 116 final Intent intent = new Intent(mContext.getApplicationContext(), 117 SimpleSaveActivity.class); 118 intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); 119 mActivity.startActivity(intent); 120 } 121 122 @Presubmit 123 @Test testAutoFillOneDatasetAndSave()124 public void testAutoFillOneDatasetAndSave() throws Exception { 125 startActivity(); 126 127 // Set service. 128 enableService(); 129 130 // Set expectations. 131 sReplier.addResponse(new CannedFillResponse.Builder() 132 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT, ID_PASSWORD) 133 .addDataset(new CannedDataset.Builder() 134 .setField(ID_INPUT, "id") 135 .setField(ID_PASSWORD, "pass") 136 .setPresentation(createPresentation("YO")) 137 .build()) 138 .build()); 139 140 // Trigger autofill. 141 mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus()); 142 sReplier.getNextFillRequest(); 143 144 // Select dataset. 145 final FillExpectation autofillExpecation = mActivity.expectAutoFill("id", "pass"); 146 mUiBot.selectDataset("YO"); 147 autofillExpecation.assertAutoFilled(); 148 149 mActivity.syncRunOnUiThread(() -> { 150 mActivity.mInput.setText("ID"); 151 mActivity.mPassword.setText("PASS"); 152 mActivity.mCommit.performClick(); 153 }); 154 final UiObject2 saveUi = mUiBot.assertUpdateShowing(SAVE_DATA_TYPE_GENERIC); 155 156 // Save it... 157 mUiBot.saveForAutofill(saveUi, true); 158 159 // ... and assert results 160 final SaveRequest saveRequest = sReplier.getNextSaveRequest(); 161 assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "ID"); 162 assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_PASSWORD), "PASS"); 163 } 164 165 @Test 166 @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough") testAutoFillOneDatasetAndSave_largeAssistStructure()167 public void testAutoFillOneDatasetAndSave_largeAssistStructure() throws Exception { 168 startActivity(); 169 170 mActivity.syncRunOnUiThread( 171 () -> mActivity.mInput.setAutofillHints(LARGE_STRING, LARGE_STRING, LARGE_STRING)); 172 173 // Set service. 174 enableService(); 175 176 // Set expectations. 177 sReplier.addResponse(new CannedFillResponse.Builder() 178 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT, ID_PASSWORD) 179 .addDataset(new CannedDataset.Builder() 180 .setField(ID_INPUT, "id") 181 .setField(ID_PASSWORD, "pass") 182 .setPresentation(createPresentation("YO")) 183 .build()) 184 .build()); 185 186 // Trigger autofill. 187 mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus()); 188 final FillRequest fillRequest = sReplier.getNextFillRequest(); 189 final ViewNode inputOnFill = findNodeByResourceId(fillRequest.structure, ID_INPUT); 190 final String[] hintsOnFill = inputOnFill.getAutofillHints(); 191 // Cannot compare these large strings directly becauise it could cause ANR 192 assertThat(hintsOnFill).hasLength(3); 193 Helper.assertEqualsToLargeString(hintsOnFill[0]); 194 Helper.assertEqualsToLargeString(hintsOnFill[1]); 195 Helper.assertEqualsToLargeString(hintsOnFill[2]); 196 197 // Select dataset. 198 final FillExpectation autofillExpecation = mActivity.expectAutoFill("id", "pass"); 199 mUiBot.selectDataset("YO"); 200 autofillExpecation.assertAutoFilled(); 201 202 mActivity.syncRunOnUiThread(() -> { 203 mActivity.mInput.setText("ID"); 204 mActivity.mPassword.setText("PASS"); 205 mActivity.mCommit.performClick(); 206 }); 207 final UiObject2 saveUi = mUiBot.assertUpdateShowing(SAVE_DATA_TYPE_GENERIC); 208 209 // Save it... 210 mUiBot.saveForAutofill(saveUi, true); 211 212 // ... and assert results 213 final SaveRequest saveRequest = sReplier.getNextSaveRequest(); 214 final ViewNode inputOnSave = findNodeByResourceId(saveRequest.structure, ID_INPUT); 215 assertTextAndValue(inputOnSave, "ID"); 216 assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_PASSWORD), "PASS"); 217 218 final String[] hintsOnSave = inputOnSave.getAutofillHints(); 219 // Cannot compare these large strings directly becauise it could cause ANR 220 assertThat(hintsOnSave).hasLength(3); 221 Helper.assertEqualsToLargeString(hintsOnSave[0]); 222 Helper.assertEqualsToLargeString(hintsOnSave[1]); 223 Helper.assertEqualsToLargeString(hintsOnSave[2]); 224 } 225 226 /** 227 * Simple test that only uses UiAutomator to interact with the activity, so it indirectly 228 * tests the integration of Autofill with Accessibility. 229 */ 230 @Test testAutoFillOneDatasetAndSave_usingUiAutomatorOnly()231 public void testAutoFillOneDatasetAndSave_usingUiAutomatorOnly() throws Exception { 232 startActivity(); 233 234 // Set service. 235 enableService(); 236 237 // Set expectations. 238 sReplier.addResponse(new CannedFillResponse.Builder() 239 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT, ID_PASSWORD) 240 .addDataset(new CannedDataset.Builder() 241 .setField(ID_INPUT, "id") 242 .setField(ID_PASSWORD, "pass") 243 .setPresentation(createPresentation("YO")) 244 .build()) 245 .build()); 246 247 // Trigger autofill. 248 mUiBot.assertShownByRelativeId(ID_INPUT).click(); 249 sReplier.getNextFillRequest(); 250 251 // Select dataset... 252 mUiBot.selectDataset("YO"); 253 254 // ...and assert autofilled values. 255 final UiObject2 input = mUiBot.assertShownByRelativeId(ID_INPUT); 256 final UiObject2 password = mUiBot.assertShownByRelativeId(ID_PASSWORD); 257 258 assertWithMessage("wrong value for 'input'").that(input.getText()).isEqualTo("id"); 259 // TODO: password field is shown as **** ; ideally we should assert it's a password 260 // field, but UiAutomator does not exposes that info. 261 final String visiblePassword = password.getText(); 262 assertWithMessage("'password' should not be visible").that(visiblePassword) 263 .isNotEqualTo("pass"); 264 assertWithMessage("wrong value for 'password'").that(visiblePassword).hasLength(4); 265 266 // Trigger save... 267 input.setText("ID"); 268 password.setText("PASS"); 269 mUiBot.assertShownByRelativeId(ID_COMMIT).click(); 270 mUiBot.updateForAutofill(true, SAVE_DATA_TYPE_GENERIC); 271 272 // ... and assert results 273 final SaveRequest saveRequest = sReplier.getNextSaveRequest(); 274 assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "ID"); 275 assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_PASSWORD), "PASS"); 276 } 277 278 @Test 279 @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough") testSave()280 public void testSave() throws Exception { 281 saveTest(false); 282 } 283 284 @Presubmit 285 @Test testSave_afterRotation()286 public void testSave_afterRotation() throws Exception { 287 assumeTrue("Rotation is supported", Helper.isRotationSupported(mContext)); 288 mUiBot.setScreenOrientation(UiBot.PORTRAIT); 289 try { 290 saveTest(true); 291 } finally { 292 try { 293 mUiBot.setScreenOrientation(UiBot.PORTRAIT); 294 cleanUpAfterScreenOrientationIsBackToPortrait(); 295 } catch (Exception e) { 296 mSafeCleanerRule.add(e); 297 } 298 } 299 } 300 saveTest(boolean rotate)301 private void saveTest(boolean rotate) throws Exception { 302 startActivity(); 303 304 // Set service. 305 enableService(); 306 307 // Set expectations. 308 sReplier.addResponse(new CannedFillResponse.Builder() 309 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT) 310 .build()); 311 312 // Trigger autofill. 313 mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus()); 314 sReplier.getNextFillRequest(); 315 316 // Trigger save. 317 mActivity.syncRunOnUiThread(() -> { 318 mActivity.mInput.setText("108"); 319 mActivity.mCommit.performClick(); 320 }); 321 UiObject2 saveUi = mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC); 322 323 if (rotate) { 324 // After the device rotates, the input field get focus and generate a new session. 325 sReplier.addResponse(CannedFillResponse.NO_RESPONSE); 326 327 mUiBot.setScreenOrientation(UiBot.LANDSCAPE); 328 saveUi = mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC); 329 } 330 331 // Save it... 332 mUiBot.saveForAutofill(saveUi, true); 333 334 // ... and assert results 335 final SaveRequest saveRequest = sReplier.getNextSaveRequest(); 336 assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "108"); 337 } 338 339 /** 340 * Emulates an app dyanmically adding the password field after username is typed. 341 */ 342 @Test 343 @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough") testPartitionedSave()344 public void testPartitionedSave() throws Exception { 345 startActivity(); 346 347 // Set service. 348 enableService(); 349 350 // 1st request 351 352 // Set expectations. 353 sReplier.addResponse(new CannedFillResponse.Builder() 354 .setRequiredSavableIds(SAVE_DATA_TYPE_USERNAME, ID_INPUT) 355 .build()); 356 357 // Trigger autofill. 358 mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus()); 359 sReplier.getNextFillRequest(); 360 361 // Set 1st field but don't commit session 362 mActivity.syncRunOnUiThread(() -> mActivity.mInput.setText("108")); 363 mUiBot.assertSaveNotShowing(); 364 365 // 2nd request 366 367 // Set expectations. 368 sReplier.addResponse(new CannedFillResponse.Builder() 369 .setRequiredSavableIds(SAVE_DATA_TYPE_USERNAME | SAVE_DATA_TYPE_PASSWORD, 370 ID_INPUT, ID_PASSWORD) 371 .build()); 372 373 // Trigger autofill. 374 mActivity.syncRunOnUiThread(() -> mActivity.mPassword.requestFocus()); 375 sReplier.getNextFillRequest(); 376 377 // Trigger save. 378 mActivity.syncRunOnUiThread(() -> { 379 mActivity.mPassword.setText("42"); 380 mActivity.mCommit.performClick(); 381 }); 382 final UiObject2 saveUi = mUiBot.assertSaveShowing(null, SAVE_DATA_TYPE_USERNAME, 383 SAVE_DATA_TYPE_PASSWORD); 384 385 // Save it... 386 mUiBot.saveForAutofill(saveUi, true); 387 388 // ... and assert results 389 final SaveRequest saveRequest = sReplier.getNextSaveRequest(); 390 assertThat(saveRequest.contexts.size()).isEqualTo(2); 391 392 assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "108"); 393 assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_PASSWORD), "42"); 394 } 395 396 /** 397 * Emulates an app using fragments to display username and password in 2 steps. 398 */ 399 @Test 400 @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough") testDelayedSave()401 public void testDelayedSave() throws Exception { 402 startActivity(); 403 404 // Set service. 405 enableService(); 406 407 // 1st fragment. 408 409 // Set expectations. 410 sReplier.addResponse(new CannedFillResponse.Builder() 411 .setSaveInfoFlags(SaveInfo.FLAG_DELAY_SAVE).build()); 412 413 // Trigger autofill. 414 mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus()); 415 sReplier.getNextFillRequest(); 416 417 // Trigger delayed save. 418 mActivity.syncRunOnUiThread(() -> { 419 mActivity.mInput.setText("108"); 420 mActivity.mCommit.performClick(); 421 }); 422 mUiBot.assertSaveNotShowing(); 423 424 // 2nd fragment. 425 426 // Set expectations. 427 sReplier.addResponse(new CannedFillResponse.Builder() 428 // Must explicitly set visitor, otherwise setRequiredSavableIds() would get the 429 // id from the 1st context 430 .setVisitor((contexts, builder) -> { 431 final AutofillId passwordId = 432 findAutofillIdByResourceId(contexts.get(1), ID_PASSWORD); 433 final AutofillId inputId = 434 findAutofillIdByResourceId(contexts.get(0), ID_INPUT); 435 builder.setSaveInfo(new SaveInfo.Builder( 436 SAVE_DATA_TYPE_USERNAME | SAVE_DATA_TYPE_PASSWORD, 437 new AutofillId[] {inputId, passwordId}) 438 .build()); 439 }) 440 .build()); 441 442 // Trigger autofill on second "fragment" 443 mActivity.syncRunOnUiThread(() -> mActivity.mPassword.requestFocus()); 444 sReplier.getNextFillRequest(); 445 446 // Trigger delayed save. 447 mActivity.syncRunOnUiThread(() -> { 448 mActivity.mPassword.setText("42"); 449 mActivity.mCommit.performClick(); 450 }); 451 452 // Save it... 453 mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_USERNAME, SAVE_DATA_TYPE_PASSWORD); 454 455 // ... and assert results 456 final SaveRequest saveRequest = sReplier.getNextSaveRequest(); 457 assertThat(saveRequest.contexts.size()).isEqualTo(2); 458 459 // Get username from 1st request. 460 final AssistStructure structure1 = saveRequest.contexts.get(0).getStructure(); 461 assertTextAndValue(findNodeByResourceId(structure1, ID_INPUT), "108"); 462 463 // Get password from 2nd request. 464 final AssistStructure structure2 = saveRequest.contexts.get(1).getStructure(); 465 assertTextAndValue(findNodeByResourceId(structure2, ID_INPUT), "108"); 466 assertTextAndValue(findNodeByResourceId(structure2, ID_PASSWORD), "42"); 467 } 468 469 @Presubmit 470 @Test testSave_launchIntent()471 public void testSave_launchIntent() throws Exception { 472 startActivity(); 473 474 // Set service. 475 enableService(); 476 477 // Set expectations. 478 sReplier.setOnSave(WelcomeActivity.createSender(mContext, "Saved by the bell")) 479 .addResponse(new CannedFillResponse.Builder() 480 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT) 481 .build()); 482 483 // Trigger autofill. 484 mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus()); 485 sReplier.getNextFillRequest(); 486 487 // Trigger save. 488 mActivity.syncRunOnUiThread(() -> { 489 mActivity.mInput.setText("108"); 490 mActivity.mCommit.performClick(); 491 492 // Disable autofill so it's not triggered again after WelcomeActivity finishes 493 // and mActivity is resumed (with focus on mInput) after the session is closed 494 mActivity.mInput.setImportantForAutofill(View.IMPORTANT_FOR_AUTOFILL_NO); 495 }); 496 497 // Save it... 498 mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC); 499 sReplier.getNextSaveRequest(); 500 501 // ... and assert activity was launched 502 WelcomeActivity.assertShowing(mUiBot, "Saved by the bell"); 503 } 504 505 @Presubmit 506 @Test testSaveThenStartNewSessionRightAwayShouldKeepSaveUi()507 public void testSaveThenStartNewSessionRightAwayShouldKeepSaveUi() throws Exception { 508 startActivity(); 509 510 // Set service. 511 enableService(); 512 513 // Set expectations. 514 sReplier.addResponse(new CannedFillResponse.Builder() 515 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT) 516 .build()); 517 518 // Trigger autofill. 519 mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus()); 520 sReplier.getNextFillRequest(); 521 522 // Trigger save. 523 mActivity.syncRunOnUiThread(() -> { 524 mActivity.mInput.setText("108"); 525 mActivity.mCommit.performClick(); 526 }); 527 528 // Make sure Save UI for 1st session was shown.... 529 mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC); 530 531 // Start new Activity to have a new autofill session 532 startActivityOnNewTask(LoginActivity.class); 533 534 // Make sure LoginActivity started... 535 mUiBot.assertShownByRelativeId(ID_USERNAME_CONTAINER); 536 537 // Set expectations. 538 sReplier.addResponse(new CannedFillResponse.Builder() 539 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_USERNAME) 540 .addDataset(new CannedDataset.Builder() 541 .setField(ID_USERNAME, "id") 542 .setField(ID_PASSWORD, "pwd") 543 .setPresentation(createPresentation("YO")) 544 .build()) 545 .build()); 546 // Trigger fill request on the LoginActivity 547 final LoginActivity act = LoginActivity.getCurrentActivity(); 548 act.syncRunOnUiThread(() -> act.forceAutofillOnUsername()); 549 sReplier.getNextFillRequest(); 550 551 // Make sure Fill UI is not shown. And Save UI for 1st session was still shown. 552 mUiBot.assertNoDatasetsEver(); 553 sReplier.assertNoUnhandledFillRequests(); 554 mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC); 555 556 mUiBot.waitForIdle(); 557 // Trigger dismiss Save UI 558 mUiBot.pressBack(); 559 560 // Make sure Save UI was not shown.... 561 mUiBot.assertSaveNotShowing(); 562 // Make sure Fill UI is shown. 563 mUiBot.assertDatasets("YO"); 564 } 565 566 @Presubmit 567 @Test testCloseSaveUiThenStartNewSessionRightAway()568 public void testCloseSaveUiThenStartNewSessionRightAway() throws Exception { 569 startActivity(); 570 571 // Set service. 572 enableService(); 573 574 // Set expectations. 575 sReplier.addResponse(new CannedFillResponse.Builder() 576 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT) 577 .build()); 578 579 // Trigger autofill. 580 mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus()); 581 sReplier.getNextFillRequest(); 582 583 // Trigger save. 584 mActivity.syncRunOnUiThread(() -> { 585 mActivity.mInput.setText("108"); 586 mActivity.mCommit.performClick(); 587 }); 588 589 // Make sure Save UI for 1st session was shown.... 590 mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC); 591 592 // Trigger dismiss Save UI 593 mUiBot.pressBack(); 594 595 // Make sure Save UI for 1st session was canceled. 596 mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC); 597 598 // ...then start the new session right away (without finishing the activity). 599 // Set expectations. 600 sReplier.addResponse(new CannedFillResponse.Builder() 601 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT) 602 .addDataset(new CannedDataset.Builder() 603 .setField(ID_INPUT, "id") 604 .setPresentation(createPresentation("YO")) 605 .build()) 606 .build()); 607 mActivity.syncRunOnUiThread(() -> { 608 mActivity.mInput.setText(""); 609 mActivity.getAutofillManager().requestAutofill(mActivity.mInput); 610 }); 611 sReplier.getNextFillRequest(); 612 613 // Make sure Fill UI is shown. 614 mUiBot.assertDatasets("YO"); 615 } 616 617 @Presubmit 618 @Test testSaveWithParcelableOnClientState()619 public void testSaveWithParcelableOnClientState() throws Exception { 620 startActivity(); 621 622 // Set service. 623 enableService(); 624 625 // Set expectations. 626 final AutofillId id = new AutofillId(42); 627 final Bundle clientState = new Bundle(); 628 clientState.putParcelable("id", id); 629 clientState.putParcelable("my_id", new MyAutofillId(id)); 630 sReplier.addResponse(new CannedFillResponse.Builder() 631 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT) 632 .setExtras(clientState) 633 .build()); 634 635 // Trigger autofill. 636 mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus()); 637 sReplier.getNextFillRequest(); 638 639 // Trigger save. 640 mActivity.syncRunOnUiThread(() -> { 641 mActivity.mInput.setText("108"); 642 mActivity.mCommit.performClick(); 643 }); 644 UiObject2 saveUi = mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC); 645 646 // Save it... 647 mUiBot.saveForAutofill(saveUi, true); 648 649 // ... and assert results 650 final SaveRequest saveRequest = sReplier.getNextSaveRequest(); 651 assertMyClientState(saveRequest.data); 652 653 // Also check fillevent history 654 final FillEventHistory history = InstrumentedAutoFillService.getFillEventHistory(1); 655 @SuppressWarnings("deprecation") 656 final Bundle deprecatedState = history.getClientState(); 657 assertMyClientState(deprecatedState); 658 assertMyClientState(history.getEvents().get(0).getClientState()); 659 } 660 assertMyClientState(Bundle data)661 private void assertMyClientState(Bundle data) { 662 // Must set proper classpath before reading the data, otherwise Bundle will use it's 663 // on class classloader, which is the framework's. 664 data.setClassLoader(getClass().getClassLoader()); 665 666 final AutofillId expectedId = new AutofillId(42); 667 final AutofillId actualId = data.getParcelable("id"); 668 assertThat(actualId).isEqualTo(expectedId); 669 final MyAutofillId actualMyId = data.getParcelable("my_id"); 670 assertThat(actualMyId).isEqualTo(new MyAutofillId(expectedId)); 671 } 672 673 @Presubmit 674 @Test testCancelPreventsSaveUiFromShowing()675 public void testCancelPreventsSaveUiFromShowing() throws Exception { 676 startActivity(); 677 678 // Set service. 679 enableService(); 680 681 // Set expectations. 682 sReplier.addResponse(new CannedFillResponse.Builder() 683 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT) 684 .build()); 685 686 // Trigger autofill. 687 mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus()); 688 sReplier.getNextFillRequest(); 689 690 // Cancel session. 691 mActivity.getAutofillManager().cancel(); 692 693 // Trigger save. 694 mActivity.syncRunOnUiThread(() -> { 695 mActivity.mInput.setText("108"); 696 mActivity.mCommit.performClick(); 697 }); 698 699 // Assert it's not showing. 700 mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC); 701 } 702 703 @Presubmit 704 @Test testDismissSave_byTappingBack()705 public void testDismissSave_byTappingBack() throws Exception { 706 startActivity(); 707 dismissSaveTest(DismissType.BACK_BUTTON); 708 } 709 710 @Test testDismissSave_byTappingHome()711 public void testDismissSave_byTappingHome() throws Exception { 712 startActivity(); 713 dismissSaveTest(DismissType.HOME_BUTTON); 714 } 715 716 @Presubmit 717 @Test testDismissSave_byTouchingOutside()718 public void testDismissSave_byTouchingOutside() throws Exception { 719 startActivity(); 720 dismissSaveTest(DismissType.TOUCH_OUTSIDE); 721 } 722 723 @Presubmit 724 @Test testDismissSave_byFocusingOutside()725 public void testDismissSave_byFocusingOutside() throws Exception { 726 startActivity(); 727 dismissSaveTest(DismissType.FOCUS_OUTSIDE); 728 } 729 dismissSaveTest(DismissType dismissType)730 private void dismissSaveTest(DismissType dismissType) throws Exception { 731 // Set service. 732 enableService(); 733 734 // Set expectations. 735 sReplier.addResponse(new CannedFillResponse.Builder() 736 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT) 737 .build()); 738 739 // Trigger autofill. 740 mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus()); 741 sReplier.getNextFillRequest(); 742 743 // Trigger save. 744 mActivity.syncRunOnUiThread(() -> { 745 mActivity.mInput.setText("108"); 746 mActivity.mCommit.performClick(); 747 }); 748 mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC); 749 750 // Then make sure it goes away when user doesn't want it.. 751 switch (dismissType) { 752 case BACK_BUTTON: 753 mUiBot.pressBack(); 754 break; 755 case HOME_BUTTON: 756 mUiBot.pressHome(); 757 break; 758 case TOUCH_OUTSIDE: 759 mUiBot.assertShownByText(TEXT_LABEL).click(); 760 break; 761 case FOCUS_OUTSIDE: 762 mActivity.syncRunOnUiThread(() -> mActivity.mLabel.requestFocus()); 763 mUiBot.assertShownByText(TEXT_LABEL).click(); 764 break; 765 default: 766 throw new IllegalArgumentException("invalid dismiss type: " + dismissType); 767 } 768 mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC); 769 } 770 771 @Presubmit 772 @Test testTapHomeWhileDatasetPickerUiIsShowing()773 public void testTapHomeWhileDatasetPickerUiIsShowing() throws Exception { 774 startActivity(); 775 enableService(); 776 final MyAutofillCallback callback = mActivity.registerCallback(); 777 778 // Set expectations. 779 sReplier.addResponse(new CannedFillResponse.Builder() 780 .addDataset(new CannedDataset.Builder() 781 .setField(ID_INPUT, "id") 782 .setField(ID_PASSWORD, "pass") 783 .setPresentation(createPresentation("YO")) 784 .build()) 785 .build()); 786 787 // Trigger autofill. 788 mUiBot.assertShownByRelativeId(ID_INPUT).click(); 789 sReplier.getNextFillRequest(); 790 mUiBot.assertDatasets("YO"); 791 callback.assertUiShownEvent(mActivity.mInput); 792 793 // Go home, you are drunk! 794 mUiBot.pressHome(); 795 mUiBot.assertNoDatasets(); 796 callback.assertUiHiddenEvent(mActivity.mInput); 797 798 // Set expectations. 799 sReplier.addResponse(new CannedFillResponse.Builder() 800 .addDataset(new CannedDataset.Builder() 801 .setField(ID_INPUT, "id") 802 .setField(ID_PASSWORD, "pass") 803 .setPresentation(createPresentation("YO2")) 804 .build()) 805 .build()); 806 807 // Switch back to the activity. 808 restartActivity(); 809 mUiBot.assertShownByText(TEXT_LABEL, Timeouts.ACTIVITY_RESURRECTION); 810 sReplier.getNextFillRequest(); 811 final UiObject2 datasetPicker = mUiBot.assertDatasets("YO2"); 812 callback.assertUiShownEvent(mActivity.mInput); 813 814 // Now autofill it. 815 final FillExpectation autofillExpecation = mActivity.expectAutoFill("id", "pass"); 816 mUiBot.selectDataset(datasetPicker, "YO2"); 817 autofillExpecation.assertAutoFilled(); 818 } 819 820 @Presubmit 821 @Test testTapHomeWhileSaveUiIsShowing()822 public void testTapHomeWhileSaveUiIsShowing() throws Exception { 823 startActivity(); 824 enableService(); 825 826 // Set expectations. 827 sReplier.addResponse(new CannedFillResponse.Builder() 828 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT) 829 .build()); 830 831 // Trigger autofill. 832 mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus()); 833 sReplier.getNextFillRequest(); 834 mUiBot.assertNoDatasetsEver(); 835 836 // Trigger save, but don't tap it. 837 mActivity.syncRunOnUiThread(() -> { 838 mActivity.mInput.setText("108"); 839 mActivity.mCommit.performClick(); 840 }); 841 mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC); 842 843 // Go home, you are drunk! 844 mUiBot.pressHome(); 845 mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC); 846 847 // Prepare the response for the next session, which will be automatically triggered 848 // when the activity is brought back. 849 sReplier.addResponse(new CannedFillResponse.Builder() 850 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT, ID_PASSWORD) 851 .addDataset(new CannedDataset.Builder() 852 .setField(ID_INPUT, "id") 853 .setField(ID_PASSWORD, "pass") 854 .setPresentation(createPresentation("YO")) 855 .build()) 856 .build()); 857 858 // Switch back to the activity. 859 restartActivity(); 860 mUiBot.assertShownByText(TEXT_LABEL, Timeouts.ACTIVITY_RESURRECTION); 861 mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC); 862 sReplier.getNextFillRequest(); 863 mUiBot.assertNoDatasetsEver(); 864 865 // Trigger and select UI. 866 mActivity.syncRunOnUiThread(() -> mActivity.mPassword.requestFocus()); 867 final FillExpectation autofillExpecation = mActivity.expectAutoFill("id", "pass"); 868 mUiBot.selectDataset("YO"); 869 870 // Assert it. 871 autofillExpecation.assertAutoFilled(); 872 } 873 874 @Override saveUiRestoredAfterTappingLinkTest(PostSaveLinkTappedAction type)875 protected void saveUiRestoredAfterTappingLinkTest(PostSaveLinkTappedAction type) 876 throws Exception { 877 startActivity(); 878 // Set service. 879 enableService(); 880 881 // Set expectations. 882 sReplier.addResponse(new CannedFillResponse.Builder() 883 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT) 884 .setSaveInfoVisitor((contexts, builder) -> builder 885 .setCustomDescription(newCustomDescription(WelcomeActivity.class))) 886 .build()); 887 888 // Trigger autofill. 889 mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus()); 890 sReplier.getNextFillRequest(); 891 892 // Trigger save. 893 mActivity.syncRunOnUiThread(() -> { 894 mActivity.mInput.setText("108"); 895 mActivity.mCommit.performClick(); 896 }); 897 final UiObject2 saveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_GENERIC); 898 899 // Tap the link. 900 tapSaveUiLink(saveUi); 901 902 // Make sure new activity is shown... 903 WelcomeActivity.assertShowingDefaultMessage(mUiBot); 904 mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC); 905 906 // .. then do something to return to previous activity... 907 switch (type) { 908 case ROTATE_THEN_TAP_BACK_BUTTON: 909 // After the device rotates, the input field get focus and generate a new session. 910 sReplier.addResponse(CannedFillResponse.NO_RESPONSE); 911 912 mUiBot.setScreenOrientation(UiBot.LANDSCAPE); 913 WelcomeActivity.assertShowingDefaultMessage(mUiBot); 914 // not breaking on purpose 915 case TAP_BACK_BUTTON: 916 // ..then go back and save it. 917 mUiBot.pressBack(); 918 break; 919 case FINISH_ACTIVITY: 920 // ..then finishes it. 921 WelcomeActivity.finishIt(); 922 break; 923 default: 924 throw new IllegalArgumentException("invalid type: " + type); 925 } 926 // Make sure previous activity is back... 927 mUiBot.assertShownByRelativeId(ID_INPUT); 928 929 // ... and tap save. 930 final UiObject2 newSaveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_GENERIC); 931 mUiBot.saveForAutofill(newSaveUi, true); 932 933 final SaveRequest saveRequest = sReplier.getNextSaveRequest(); 934 assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "108"); 935 } 936 937 @Override cleanUpAfterScreenOrientationIsBackToPortrait()938 protected void cleanUpAfterScreenOrientationIsBackToPortrait() throws Exception { 939 sReplier.getNextFillRequest(); 940 } 941 942 @Override tapLinkThenTapBackThenStartOverTest(PostSaveLinkTappedAction action, boolean manualRequest)943 protected void tapLinkThenTapBackThenStartOverTest(PostSaveLinkTappedAction action, 944 boolean manualRequest) throws Exception { 945 startActivity(); 946 // Set service. 947 enableService(); 948 949 // Set expectations. 950 sReplier.addResponse(new CannedFillResponse.Builder() 951 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT) 952 .setSaveInfoVisitor((contexts, builder) -> builder 953 .setCustomDescription(newCustomDescription(WelcomeActivity.class))) 954 .build()); 955 956 // Trigger autofill. 957 mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus()); 958 sReplier.getNextFillRequest(); 959 960 // Trigger save. 961 mActivity.syncRunOnUiThread(() -> { 962 mActivity.mInput.setText("108"); 963 mActivity.mCommit.performClick(); 964 }); 965 final UiObject2 saveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_GENERIC); 966 967 // Tap the link. 968 tapSaveUiLink(saveUi); 969 970 // Make sure new activity is shown. 971 WelcomeActivity.assertShowingDefaultMessage(mUiBot); 972 mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC); 973 974 // Tap back to restore the Save UI... 975 mUiBot.pressBack(); 976 // Make sure previous activity is back... 977 mUiBot.assertShownByRelativeId(ID_LABEL); 978 979 // ...but don't tap it... 980 final UiObject2 saveUi2 = mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC); 981 982 // ...instead, do something to dismiss it: 983 switch (action) { 984 case TOUCH_OUTSIDE: 985 mUiBot.assertShownByRelativeId(ID_LABEL).longClick(); 986 break; 987 case TAP_NO_ON_SAVE_UI: 988 mUiBot.saveForAutofill(saveUi2, false); 989 break; 990 case TAP_YES_ON_SAVE_UI: 991 mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC); 992 final SaveRequest saveRequest = sReplier.getNextSaveRequest(); 993 assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "108"); 994 break; 995 default: 996 throw new IllegalArgumentException("invalid action: " + action); 997 } 998 mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC); 999 1000 // Now triggers a new session and do business as usual... 1001 sReplier.addResponse(new CannedFillResponse.Builder() 1002 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT) 1003 .build()); 1004 1005 // Trigger autofill. 1006 if (manualRequest) { 1007 mActivity.syncRunOnUiThread( 1008 () -> mActivity.getAutofillManager().requestAutofill(mActivity.mInput)); 1009 } else { 1010 mActivity.syncRunOnUiThread(() -> mActivity.mPassword.requestFocus()); 1011 } 1012 1013 sReplier.getNextFillRequest(); 1014 1015 // Trigger save. 1016 mActivity.syncRunOnUiThread(() -> { 1017 mActivity.mInput.setText("42"); 1018 mActivity.mCommit.performClick(); 1019 }); 1020 1021 // Save it... 1022 mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC); 1023 1024 // ... and assert results 1025 final SaveRequest saveRequest = sReplier.getNextSaveRequest(); 1026 assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "42"); 1027 } 1028 1029 @Override saveUiCancelledAfterTappingLinkTest(PostSaveLinkTappedAction type)1030 protected void saveUiCancelledAfterTappingLinkTest(PostSaveLinkTappedAction type) 1031 throws Exception { 1032 startActivity(false); 1033 // Set service. 1034 enableService(); 1035 1036 // Set expectations. 1037 sReplier.addResponse(new CannedFillResponse.Builder() 1038 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT) 1039 .setSaveInfoVisitor((contexts, builder) -> builder 1040 .setCustomDescription(newCustomDescription(WelcomeActivity.class))) 1041 .build()); 1042 1043 // Trigger autofill. 1044 mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus()); 1045 sReplier.getNextFillRequest(); 1046 1047 // Trigger save. 1048 mActivity.syncRunOnUiThread(() -> { 1049 mActivity.mInput.setText("108"); 1050 mActivity.mCommit.performClick(); 1051 }); 1052 final UiObject2 saveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_GENERIC); 1053 1054 // Tap the link. 1055 tapSaveUiLink(saveUi); 1056 // Make sure new activity is shown... 1057 WelcomeActivity.assertShowingDefaultMessage(mUiBot); 1058 mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC); 1059 1060 switch (type) { 1061 case LAUNCH_PREVIOUS_ACTIVITY: 1062 startActivityOnNewTask(SimpleSaveActivity.class); 1063 break; 1064 case LAUNCH_NEW_ACTIVITY: 1065 // Launch a 3rd activity... 1066 startActivityOnNewTask(LoginActivity.class); 1067 mUiBot.assertShownByRelativeId(ID_USERNAME_CONTAINER); 1068 // ...then go back 1069 mUiBot.pressBack(); 1070 break; 1071 default: 1072 throw new IllegalArgumentException("invalid type: " + type); 1073 } 1074 // Make sure right activity is showing 1075 mUiBot.assertShownByRelativeId(ID_INPUT, Timeouts.ACTIVITY_RESURRECTION); 1076 1077 mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC); 1078 } 1079 1080 @Presubmit 1081 @Test 1082 @AppModeFull(reason = "Service-specific test") testSelectedDatasetsAreSentOnSaveRequest()1083 public void testSelectedDatasetsAreSentOnSaveRequest() throws Exception { 1084 startActivity(); 1085 1086 // Set service. 1087 enableService(); 1088 1089 // Set expectations. 1090 sReplier.addResponse(new CannedFillResponse.Builder() 1091 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT, ID_PASSWORD) 1092 // Added on reversed order on purpose 1093 .addDataset(new CannedDataset.Builder() 1094 .setId("D2") 1095 .setField(ID_INPUT, "id again") 1096 .setField(ID_PASSWORD, "pass") 1097 .setPresentation(createPresentation("D2")) 1098 .build()) 1099 .addDataset(new CannedDataset.Builder() 1100 .setId("D1") 1101 .setField(ID_INPUT, "id") 1102 .setPresentation(createPresentation("D1")) 1103 .build()) 1104 .build()); 1105 1106 // Trigger autofill. 1107 mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus()); 1108 sReplier.getNextFillRequest(); 1109 1110 // Select 1st dataset. 1111 final FillExpectation autofillExpecation1 = mActivity.expectAutoFill("id"); 1112 final UiObject2 picker1 = mUiBot.assertDatasets("D2", "D1"); 1113 mUiBot.selectDataset(picker1, "D1"); 1114 autofillExpecation1.assertAutoFilled(); 1115 1116 // Select 2nd dataset. 1117 mActivity.syncRunOnUiThread(() -> mActivity.mPassword.requestFocus()); 1118 final FillExpectation autofillExpecation2 = mActivity.expectAutoFill("id again", "pass"); 1119 final UiObject2 picker2 = mUiBot.assertDatasets("D2"); 1120 mUiBot.selectDataset(picker2, "D2"); 1121 autofillExpecation2.assertAutoFilled(); 1122 1123 mActivity.syncRunOnUiThread(() -> { 1124 mActivity.mInput.setText("ID"); 1125 mActivity.mPassword.setText("PASS"); 1126 mActivity.mCommit.performClick(); 1127 }); 1128 final UiObject2 saveUi = mUiBot.assertUpdateShowing(SAVE_DATA_TYPE_GENERIC); 1129 1130 // Save it... 1131 mUiBot.saveForAutofill(saveUi, true); 1132 1133 // ... and assert results 1134 final SaveRequest saveRequest = sReplier.getNextSaveRequest(); 1135 assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "ID"); 1136 assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_PASSWORD), "PASS"); 1137 assertThat(saveRequest.datasetIds).containsExactly("D1", "D2").inOrder(); 1138 } 1139 1140 @Override tapLinkLaunchTrampolineActivityThenTapBackAndStartNewSessionTest()1141 protected void tapLinkLaunchTrampolineActivityThenTapBackAndStartNewSessionTest() 1142 throws Exception { 1143 // Prepare activity. 1144 startActivity(); 1145 mActivity.syncRunOnUiThread(() -> mActivity.mInput.getRootView() 1146 .setImportantForAutofill(View.IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS) 1147 ); 1148 1149 // Set service. 1150 enableService(); 1151 1152 // Set expectations. 1153 sReplier.addResponse(new CannedFillResponse.Builder() 1154 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT) 1155 .setSaveInfoVisitor((contexts, builder) -> builder 1156 .setCustomDescription( 1157 newCustomDescription(TrampolineWelcomeActivity.class))) 1158 .build()); 1159 1160 // Trigger autofill. 1161 mActivity.syncRunOnUiThread( 1162 () -> mActivity.getAutofillManager().requestAutofill(mActivity.mInput)); 1163 sReplier.getNextFillRequest(); 1164 1165 // Trigger save. 1166 mActivity.syncRunOnUiThread(() -> { 1167 mActivity.mInput.setText("108"); 1168 mActivity.mCommit.performClick(); 1169 }); 1170 final UiObject2 saveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_GENERIC); 1171 1172 // Tap the link. 1173 tapSaveUiLink(saveUi); 1174 1175 // Make sure new activity is shown... 1176 WelcomeActivity.assertShowingDefaultMessage(mUiBot); 1177 1178 // Save UI should be showing as well, since Trampoline finished. 1179 mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC); 1180 1181 // Dismiss Save Dialog 1182 mUiBot.pressBack(); 1183 // Go back and make sure it's showing the right activity. 1184 mUiBot.pressBack(); 1185 mUiBot.assertShownByRelativeId(ID_LABEL); 1186 1187 // Now start a new session. 1188 sReplier.addResponse(new CannedFillResponse.Builder() 1189 .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_PASSWORD) 1190 .build()); 1191 1192 // Trigger autofill on password 1193 mActivity.syncRunOnUiThread( 1194 () -> mActivity.getAutofillManager().requestAutofill(mActivity.mPassword)); 1195 sReplier.getNextFillRequest(); 1196 1197 mActivity.syncRunOnUiThread(() -> { 1198 mActivity.mPassword.setText("42"); 1199 mActivity.mCommit.performClick(); 1200 }); 1201 mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD); 1202 final SaveRequest saveRequest = sReplier.getNextSaveRequest(); 1203 assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "108"); 1204 assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_PASSWORD), "42"); 1205 } 1206 1207 @Presubmit 1208 @Test testSanitizeOnSaveWhenAppChangeValues()1209 public void testSanitizeOnSaveWhenAppChangeValues() throws Exception { 1210 startActivity(); 1211 1212 // Set listeners that will change the saved value 1213 new AntiTrimmerTextWatcher(mActivity.mInput); 1214 new AntiTrimmerTextWatcher(mActivity.mPassword); 1215 1216 // Set service. 1217 enableService(); 1218 1219 // Set expectations. 1220 sReplier.addResponse(new CannedFillResponse.Builder() 1221 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT) 1222 .setSaveInfoVisitor((contexts, builder) -> { 1223 final FillContext context = contexts.get(0); 1224 final AutofillId inputId = findAutofillIdByResourceId(context, ID_INPUT); 1225 final AutofillId passwordId = findAutofillIdByResourceId(context, ID_PASSWORD); 1226 builder.addSanitizer(new TextValueSanitizer(TRIMMER_PATTERN, "$1"), inputId, 1227 passwordId); 1228 }) 1229 .build()); 1230 1231 // Trigger autofill. 1232 mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus()); 1233 sReplier.getNextFillRequest(); 1234 1235 // Trigger save. 1236 mActivity.syncRunOnUiThread(() -> { 1237 mActivity.mInput.setText("id"); 1238 mActivity.mPassword.setText("pass"); 1239 mActivity.mCommit.performClick(); 1240 }); 1241 1242 // Save it... 1243 mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC); 1244 1245 // ... and assert results 1246 final SaveRequest saveRequest = sReplier.getNextSaveRequest(); 1247 assertTextValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "id"); 1248 assertTextValue(findNodeByResourceId(saveRequest.structure, ID_PASSWORD), "pass"); 1249 } 1250 1251 @Test 1252 @AppModeFull(reason = "testSanitizeOnSaveWhenAppChangeValues() is enough") testSanitizeOnSaveNoChange()1253 public void testSanitizeOnSaveNoChange() throws Exception { 1254 startActivity(); 1255 1256 // Set service. 1257 enableService(); 1258 1259 // Set expectations. 1260 sReplier.addResponse(new CannedFillResponse.Builder() 1261 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT) 1262 .setOptionalSavableIds(ID_PASSWORD) 1263 .setSaveInfoVisitor((contexts, builder) -> { 1264 final FillContext context = contexts.get(0); 1265 final AutofillId inputId = findAutofillIdByResourceId(context, ID_INPUT); 1266 final AutofillId passwordId = findAutofillIdByResourceId(context, ID_PASSWORD); 1267 builder.addSanitizer(new TextValueSanitizer(TRIMMER_PATTERN, "$1"), inputId, 1268 passwordId); 1269 }) 1270 .build()); 1271 1272 // Trigger autofill. 1273 mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus()); 1274 sReplier.getNextFillRequest(); 1275 mUiBot.assertNoDatasetsEver(); 1276 1277 // Trigger save. 1278 mActivity.syncRunOnUiThread(() -> { 1279 mActivity.mInput.setText("#id#"); 1280 mActivity.mPassword.setText("#pass#"); 1281 mActivity.mCommit.performClick(); 1282 }); 1283 1284 // Save it... 1285 mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_GENERIC); 1286 1287 // ... and assert results 1288 final SaveRequest saveRequest = sReplier.getNextSaveRequest(); 1289 assertTextValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "id"); 1290 assertTextValue(findNodeByResourceId(saveRequest.structure, ID_PASSWORD), "pass"); 1291 } 1292 1293 @Test 1294 @AppModeFull(reason = "testSanitizeOnSaveWhenAppChangeValues() is enough") testDontSaveWhenSanitizedValueForRequiredFieldDidntChange()1295 public void testDontSaveWhenSanitizedValueForRequiredFieldDidntChange() throws Exception { 1296 startActivity(); 1297 1298 // Set listeners that will change the saved value 1299 new AntiTrimmerTextWatcher(mActivity.mInput); 1300 new AntiTrimmerTextWatcher(mActivity.mPassword); 1301 1302 // Set service. 1303 enableService(); 1304 1305 // Set expectations. 1306 sReplier.addResponse(new CannedFillResponse.Builder() 1307 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT, ID_PASSWORD) 1308 .setSaveInfoVisitor((contexts, builder) -> { 1309 final FillContext context = contexts.get(0); 1310 final AutofillId inputId = findAutofillIdByResourceId(context, ID_INPUT); 1311 final AutofillId passwordId = findAutofillIdByResourceId(context, ID_PASSWORD); 1312 builder.addSanitizer(new TextValueSanitizer(TRIMMER_PATTERN, "$1"), inputId, 1313 passwordId); 1314 }) 1315 .addDataset(new CannedDataset.Builder() 1316 .setField(ID_INPUT, "id") 1317 .setField(ID_PASSWORD, "pass") 1318 .setPresentation(createPresentation("YO")) 1319 .build()) 1320 .build()); 1321 1322 // Trigger autofill. 1323 mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus()); 1324 sReplier.getNextFillRequest(); 1325 1326 mActivity.syncRunOnUiThread(() -> { 1327 mActivity.mInput.setText("id"); 1328 mActivity.mPassword.setText("pass"); 1329 mActivity.mCommit.performClick(); 1330 }); 1331 1332 mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC); 1333 } 1334 1335 @Test 1336 @AppModeFull(reason = "testSanitizeOnSaveWhenAppChangeValues() is enough") testDontSaveWhenSanitizedValueForOptionalFieldDidntChange()1337 public void testDontSaveWhenSanitizedValueForOptionalFieldDidntChange() throws Exception { 1338 startActivity(); 1339 1340 // Set service. 1341 enableService(); 1342 1343 // Set expectations. 1344 sReplier.addResponse(new CannedFillResponse.Builder() 1345 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT) 1346 .setOptionalSavableIds(ID_PASSWORD) 1347 .setSaveInfoVisitor((contexts, builder) -> { 1348 final FillContext context = contexts.get(0); 1349 final AutofillId passwordId = findAutofillIdByResourceId(context, ID_PASSWORD); 1350 builder.addSanitizer(new TextValueSanitizer(Pattern.compile("(pass) "), "$1"), 1351 passwordId); 1352 }) 1353 .addDataset(new CannedDataset.Builder() 1354 .setField(ID_INPUT, "id") 1355 .setField(ID_PASSWORD, "pass") 1356 .setPresentation(createPresentation("YO")) 1357 .build()) 1358 .build()); 1359 1360 // Trigger autofill. 1361 mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus()); 1362 sReplier.getNextFillRequest(); 1363 1364 mActivity.syncRunOnUiThread(() -> { 1365 mActivity.mInput.setText("id"); 1366 mActivity.mPassword.setText("#pass#"); 1367 mActivity.mCommit.performClick(); 1368 }); 1369 1370 mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC); 1371 } 1372 1373 @Test 1374 @AppModeFull(reason = "testSanitizeOnSaveWhenAppChangeValues() is enough") testDontSaveWhenRequiredFieldFailedSanitization()1375 public void testDontSaveWhenRequiredFieldFailedSanitization() throws Exception { 1376 startActivity(); 1377 1378 // Set service. 1379 enableService(); 1380 1381 // Set expectations. 1382 sReplier.addResponse(new CannedFillResponse.Builder() 1383 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT, ID_PASSWORD) 1384 .setSaveInfoVisitor((contexts, builder) -> { 1385 final FillContext context = contexts.get(0); 1386 final AutofillId inputId = findAutofillIdByResourceId(context, ID_INPUT); 1387 final AutofillId passwordId = findAutofillIdByResourceId(context, ID_PASSWORD); 1388 builder.addSanitizer(new TextValueSanitizer(Pattern.compile("dude"), "$1"), 1389 inputId, passwordId); 1390 }) 1391 .addDataset(new CannedDataset.Builder() 1392 .setField(ID_INPUT, "#id#") 1393 .setField(ID_PASSWORD, "#pass#") 1394 .setPresentation(createPresentation("YO")) 1395 .build()) 1396 .build()); 1397 1398 // Trigger autofill. 1399 mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus()); 1400 sReplier.getNextFillRequest(); 1401 1402 mActivity.syncRunOnUiThread(() -> { 1403 mActivity.mInput.setText("id"); 1404 mActivity.mPassword.setText("pass"); 1405 mActivity.mCommit.performClick(); 1406 }); 1407 1408 mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC); 1409 } 1410 1411 @Test 1412 @AppModeFull(reason = "testSanitizeOnSaveWhenAppChangeValues() is enough") testDontSaveWhenOptionalFieldFailedSanitization()1413 public void testDontSaveWhenOptionalFieldFailedSanitization() throws Exception { 1414 startActivity(); 1415 1416 // Set service. 1417 enableService(); 1418 1419 // Set expectations. 1420 sReplier.addResponse(new CannedFillResponse.Builder() 1421 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT) 1422 .setOptionalSavableIds(ID_PASSWORD) 1423 .setSaveInfoVisitor((contexts, builder) -> { 1424 final FillContext context = contexts.get(0); 1425 final AutofillId inputId = findAutofillIdByResourceId(context, ID_INPUT); 1426 final AutofillId passwordId = findAutofillIdByResourceId(context, ID_PASSWORD); 1427 builder.addSanitizer(new TextValueSanitizer(Pattern.compile("dude"), "$1"), 1428 inputId, passwordId); 1429 1430 }) 1431 .addDataset(new CannedDataset.Builder() 1432 .setField(ID_INPUT, "id") 1433 .setField(ID_PASSWORD, "#pass#") 1434 .setPresentation(createPresentation("YO")) 1435 .build()) 1436 .build()); 1437 1438 // Trigger autofill. 1439 mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus()); 1440 sReplier.getNextFillRequest(); 1441 1442 mActivity.syncRunOnUiThread(() -> { 1443 mActivity.mInput.setText("id"); 1444 mActivity.mPassword.setText("pass"); 1445 mActivity.mCommit.performClick(); 1446 }); 1447 1448 mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC); 1449 } 1450 1451 @Test 1452 @AppModeFull(reason = "testSanitizeOnSaveWhenAppChangeValues() is enough") testDontSaveWhenInitialValueAndNoUserInputAndServiceDatasets()1453 public void testDontSaveWhenInitialValueAndNoUserInputAndServiceDatasets() throws Throwable { 1454 // Prepare activitiy. 1455 startActivity(); 1456 mActivity.syncRunOnUiThread(() -> { 1457 // NOTE: input's value must be a subset of the dataset value, otherwise the dataset 1458 // picker is filtered out 1459 mActivity.mInput.setText("f"); 1460 mActivity.mPassword.setText("b"); 1461 }); 1462 1463 // Set service. 1464 enableService(); 1465 1466 // Set expectations. 1467 sReplier.addResponse(new CannedFillResponse.Builder() 1468 .addDataset(new CannedDataset.Builder() 1469 .setField(ID_INPUT, "foo") 1470 .setField(ID_PASSWORD, "bar") 1471 .setPresentation(createPresentation("The Dude")) 1472 .build()) 1473 .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_INPUT, ID_PASSWORD).build()); 1474 1475 // Trigger auto-fill. 1476 mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus()); 1477 sReplier.getNextFillRequest(); 1478 mUiBot.assertDatasets("The Dude"); 1479 1480 // Trigger save. 1481 mActivity.getAutofillManager().commit(); 1482 1483 // Assert it's not showing. 1484 mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD); 1485 } 1486 1487 enum SetTextCondition { 1488 NORMAL, 1489 HAS_SESSION, 1490 EMPTY_TEXT, 1491 FOCUSED, 1492 NOT_IMPORTANT_FOR_AUTOFILL, 1493 INVISIBLE 1494 } 1495 1496 /** 1497 * Tests scenario when a text field's text is set automatically, it should trigger autofill and 1498 * show Save UI. 1499 */ 1500 @Presubmit 1501 @Test testShowSaveUiWhenSetTextAutomatically()1502 public void testShowSaveUiWhenSetTextAutomatically() throws Exception { 1503 triggerAutofillWhenSetTextAutomaticallyTest(SetTextCondition.NORMAL); 1504 } 1505 1506 /** 1507 * Tests scenario when a text field's text is set automatically, it should not trigger autofill 1508 * when there is an existing session. 1509 */ 1510 @Presubmit 1511 @Test testNotTriggerAutofillWhenSetTextWhileSessionExists()1512 public void testNotTriggerAutofillWhenSetTextWhileSessionExists() throws Exception { 1513 triggerAutofillWhenSetTextAutomaticallyTest(SetTextCondition.HAS_SESSION); 1514 } 1515 1516 /** 1517 * Tests scenario when a text field's text is set automatically, it should not trigger autofill 1518 * when the text is empty. 1519 */ 1520 @Presubmit 1521 @Test testNotTriggerAutofillWhenSetTextWhileEmptyText()1522 public void testNotTriggerAutofillWhenSetTextWhileEmptyText() throws Exception { 1523 triggerAutofillWhenSetTextAutomaticallyTest(SetTextCondition.EMPTY_TEXT); 1524 } 1525 1526 /** 1527 * Tests scenario when a text field's text is set automatically, it should not trigger autofill 1528 * when the field is focused. 1529 */ 1530 @Presubmit 1531 @Test testNotTriggerAutofillWhenSetTextWhileFocused()1532 public void testNotTriggerAutofillWhenSetTextWhileFocused() throws Exception { 1533 triggerAutofillWhenSetTextAutomaticallyTest(SetTextCondition.FOCUSED); 1534 } 1535 1536 /** 1537 * Tests scenario when a text field's text is set automatically, it should not trigger autofill 1538 * when the field is not important for autofill. 1539 */ 1540 @Presubmit 1541 @Test testNotTriggerAutofillWhenSetTextWhileNotImportantForAutofill()1542 public void testNotTriggerAutofillWhenSetTextWhileNotImportantForAutofill() throws Exception { 1543 triggerAutofillWhenSetTextAutomaticallyTest(SetTextCondition.NOT_IMPORTANT_FOR_AUTOFILL); 1544 } 1545 1546 /** 1547 * Tests scenario when a text field's text is set automatically, it should not trigger autofill 1548 * when the field is not visible. 1549 */ 1550 @Presubmit 1551 @Test testNotTriggerAutofillWhenSetTextWhileInvisible()1552 public void testNotTriggerAutofillWhenSetTextWhileInvisible() throws Exception { 1553 triggerAutofillWhenSetTextAutomaticallyTest(SetTextCondition.INVISIBLE); 1554 } 1555 triggerAutofillWhenSetTextAutomaticallyTest(SetTextCondition condition)1556 private void triggerAutofillWhenSetTextAutomaticallyTest(SetTextCondition condition) 1557 throws Exception { 1558 startActivity(); 1559 1560 // Set service. 1561 enableService(); 1562 1563 // Set expectations. 1564 sReplier.addResponse(new CannedFillResponse.Builder() 1565 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT) 1566 .build()); 1567 1568 CharSequence inputText = "108"; 1569 1570 switch (condition) { 1571 case NORMAL: 1572 // Nothing. 1573 break; 1574 case HAS_SESSION: 1575 mActivity.syncRunOnUiThread(() -> { 1576 mActivity.mInput.setText("100"); 1577 }); 1578 sReplier.getNextFillRequest(); 1579 break; 1580 case EMPTY_TEXT: 1581 inputText = ""; 1582 break; 1583 case FOCUSED: 1584 mActivity.syncRunOnUiThread(() -> { 1585 mActivity.mInput.requestFocus(); 1586 }); 1587 sReplier.getNextFillRequest(); 1588 break; 1589 case NOT_IMPORTANT_FOR_AUTOFILL: 1590 mActivity.syncRunOnUiThread(() -> { 1591 mActivity.mInput.setImportantForAutofill(View.IMPORTANT_FOR_AUTOFILL_NO); 1592 }); 1593 break; 1594 case INVISIBLE: 1595 mActivity.syncRunOnUiThread(() -> { 1596 mActivity.mInput.setVisibility(View.INVISIBLE); 1597 }); 1598 break; 1599 default: 1600 throw new IllegalArgumentException("invalid condition: " + condition); 1601 } 1602 1603 // Trigger autofill by setting text. 1604 final CharSequence text = inputText; 1605 mActivity.syncRunOnUiThread(() -> { 1606 mActivity.mInput.setText(text); 1607 }); 1608 1609 if (condition == SetTextCondition.NORMAL) { 1610 sReplier.getNextFillRequest(); 1611 1612 mActivity.syncRunOnUiThread(() -> { 1613 mActivity.mInput.setText("100"); 1614 mActivity.mCommit.performClick(); 1615 }); 1616 1617 mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC); 1618 } else { 1619 sReplier.assertOnFillRequestNotCalled(); 1620 } 1621 } 1622 1623 @Presubmit 1624 @Test testExplicitlySaveButton()1625 public void testExplicitlySaveButton() throws Exception { 1626 explicitlySaveButtonTest(false, 0); 1627 } 1628 1629 @Presubmit 1630 @Test testExplicitlySaveButtonWhenAppClearFields()1631 public void testExplicitlySaveButtonWhenAppClearFields() throws Exception { 1632 explicitlySaveButtonTest(true, 0); 1633 } 1634 1635 @Presubmit 1636 @Test testExplicitlySaveButtonOnly()1637 public void testExplicitlySaveButtonOnly() throws Exception { 1638 explicitlySaveButtonTest(false, SaveInfo.FLAG_DONT_SAVE_ON_FINISH); 1639 } 1640 1641 /** 1642 * Tests scenario where service explicitly indicates which button is used to save. 1643 */ explicitlySaveButtonTest(boolean clearFieldsOnSubmit, int flags)1644 private void explicitlySaveButtonTest(boolean clearFieldsOnSubmit, int flags) throws Exception { 1645 final boolean testBitmap = false; 1646 startActivity(); 1647 mActivity.setAutoCommit(false); 1648 mActivity.setClearFieldsOnSubmit(clearFieldsOnSubmit); 1649 1650 // Set service. 1651 enableService(); 1652 1653 // Set expectations. 1654 sReplier.addResponse(new CannedFillResponse.Builder() 1655 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT) 1656 .setSaveTriggerId(mActivity.mCommit.getAutofillId()) 1657 .setSaveInfoFlags(flags) 1658 .build()); 1659 1660 // Trigger autofill. 1661 mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus()); 1662 sReplier.getNextFillRequest(); 1663 1664 // Trigger save. 1665 mActivity.syncRunOnUiThread(() -> mActivity.mInput.setText("108")); 1666 1667 // Take a screenshot to make sure button doesn't disappear. 1668 final String commitBefore = mUiBot.assertShownByRelativeId(ID_COMMIT).getText(); 1669 assertThat(commitBefore.toUpperCase()).isEqualTo("COMMIT"); 1670 // Disable unnecessary screenshot tests as takeScreenshot() fails on some device. 1671 1672 final Bitmap screenshotBefore = testBitmap ? mActivity.takeScreenshot(mActivity.mCommit) 1673 : null; 1674 1675 // Save it... 1676 mActivity.syncRunOnUiThread(() -> mActivity.mCommit.performClick()); 1677 final UiObject2 saveUi = mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC); 1678 mUiBot.saveForAutofill(saveUi, true); 1679 1680 // Make sure save button is showning (it was removed on earlier versions of the feature) 1681 final String commitAfter = mUiBot.assertShownByRelativeId(ID_COMMIT).getText(); 1682 assertThat(commitAfter.toUpperCase()).isEqualTo("COMMIT"); 1683 final Bitmap screenshotAfter = testBitmap ? mActivity.takeScreenshot(mActivity.mCommit) 1684 : null; 1685 1686 // ... and assert results 1687 final SaveRequest saveRequest = sReplier.getNextSaveRequest(); 1688 assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "108"); 1689 1690 if (testBitmap) { 1691 Helper.assertBitmapsAreSame("commit-button", screenshotBefore, screenshotAfter); 1692 } 1693 } 1694 1695 @Override tapLinkAfterUpdateAppliedTest(boolean updateLinkView)1696 protected void tapLinkAfterUpdateAppliedTest(boolean updateLinkView) throws Exception { 1697 startActivity(); 1698 // Set service. 1699 enableService(); 1700 1701 // Set expectations. 1702 sReplier.addResponse(new CannedFillResponse.Builder() 1703 .setSaveInfoVisitor((contexts, builder) -> { 1704 // Set response with custom description 1705 final AutofillId id = findAutofillIdByResourceId(contexts.get(0), ID_INPUT); 1706 final CustomDescription.Builder customDescription = 1707 newCustomDescriptionBuilder(WelcomeActivity.class); 1708 final RemoteViews update = newTemplate(); 1709 if (updateLinkView) { 1710 update.setCharSequence(R.id.link, "setText", "TAP ME IF YOU CAN"); 1711 } else { 1712 update.setCharSequence(R.id.static_text, "setText", "ME!"); 1713 } 1714 Validator validCondition = new RegexValidator(id, Pattern.compile(".*")); 1715 customDescription.batchUpdate(validCondition, 1716 new BatchUpdates.Builder().updateTemplate(update).build()); 1717 1718 builder.setCustomDescription(customDescription.build()); 1719 }) 1720 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT) 1721 .build()); 1722 1723 // Trigger autofill. 1724 mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus()); 1725 sReplier.getNextFillRequest(); 1726 // Trigger save. 1727 mActivity.syncRunOnUiThread(() -> { 1728 mActivity.mInput.setText("108"); 1729 mActivity.mCommit.performClick(); 1730 }); 1731 final UiObject2 saveUi; 1732 if (updateLinkView) { 1733 saveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_GENERIC, "TAP ME IF YOU CAN"); 1734 } else { 1735 saveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_GENERIC); 1736 final UiObject2 changed = saveUi.findObject(By.res(mPackageName, ID_STATIC_TEXT)); 1737 assertThat(changed.getText()).isEqualTo("ME!"); 1738 } 1739 1740 // Tap the link. 1741 tapSaveUiLink(saveUi); 1742 1743 // Make sure new activity is shown... 1744 WelcomeActivity.assertShowingDefaultMessage(mUiBot); 1745 mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC); 1746 } 1747 1748 enum DescriptionType { 1749 SUCCINCT, 1750 CUSTOM, 1751 } 1752 1753 /** 1754 * Tests scenarios when user taps a span in the custom description, then the new activity 1755 * finishes: 1756 * the Save UI should have been restored. 1757 */ 1758 @Presubmit 1759 @Test 1760 @AppModeFull(reason = "No real use case for instant mode af service") testTapUrlSpanOnCustomDescription_thenTapBack()1761 public void testTapUrlSpanOnCustomDescription_thenTapBack() throws Exception { 1762 saveUiRestoredAfterTappingSpanTest(DescriptionType.CUSTOM, 1763 ViewActionActivity.ActivityCustomAction.NORMAL_ACTIVITY); 1764 } 1765 1766 /** 1767 * Tests scenarios when user taps a span in the succinct description, then the new activity 1768 * finishes: 1769 * the Save UI should have been restored. 1770 */ 1771 @Presubmit 1772 @Test 1773 @AppModeFull(reason = "No real use case for instant mode af service") testTapUrlSpanOnSuccinctDescription_thenTapBack()1774 public void testTapUrlSpanOnSuccinctDescription_thenTapBack() throws Exception { 1775 saveUiRestoredAfterTappingSpanTest(DescriptionType.SUCCINCT, 1776 ViewActionActivity.ActivityCustomAction.NORMAL_ACTIVITY); 1777 } 1778 1779 /** 1780 * Tests scenarios when user taps a span in the custom description, then the new activity 1781 * starts an another activity then it finishes: 1782 * the Save UI should have been restored. 1783 */ 1784 @Presubmit 1785 @Test 1786 @AppModeFull(reason = "No real use case for instant mode af service") testTapUrlSpanOnCustomDescription_forwardAnotherActivityThenTapBack()1787 public void testTapUrlSpanOnCustomDescription_forwardAnotherActivityThenTapBack() 1788 throws Exception { 1789 saveUiRestoredAfterTappingSpanTest(DescriptionType.CUSTOM, 1790 ViewActionActivity.ActivityCustomAction.FAST_FORWARD_ANOTHER_ACTIVITY); 1791 } 1792 1793 /** 1794 * Tests scenarios when user taps a span in the succinct description, then the new activity 1795 * starts an another activity then it finishes: 1796 * the Save UI should have been restored. 1797 */ 1798 @Presubmit 1799 @Test 1800 @AppModeFull(reason = "No real use case for instant mode af service") testTapUrlSpanOnSuccinctDescription_forwardAnotherActivityThenTapBack()1801 public void testTapUrlSpanOnSuccinctDescription_forwardAnotherActivityThenTapBack() 1802 throws Exception { 1803 saveUiRestoredAfterTappingSpanTest(DescriptionType.SUCCINCT, 1804 ViewActionActivity.ActivityCustomAction.FAST_FORWARD_ANOTHER_ACTIVITY); 1805 } 1806 1807 /** 1808 * Tests scenarios when user taps a span in the custom description, then the new activity 1809 * stops but does not finish: 1810 * the Save UI should have been restored. 1811 */ 1812 @Presubmit 1813 @Test 1814 @AppModeFull(reason = "No real use case for instant mode af service") testTapUrlSpanOnCustomDescription_tapBackWithoutFinish()1815 public void testTapUrlSpanOnCustomDescription_tapBackWithoutFinish() throws Exception { 1816 saveUiRestoredAfterTappingSpanTest(DescriptionType.CUSTOM, 1817 ViewActionActivity.ActivityCustomAction.TAP_BACK_WITHOUT_FINISH); 1818 } 1819 1820 /** 1821 * Tests scenarios when user taps a span in the succinct description, then the new activity 1822 * stops but does not finish: 1823 * the Save UI should have been restored. 1824 */ 1825 @Presubmit 1826 @Test 1827 @AppModeFull(reason = "No real use case for instant mode af service") testTapUrlSpanOnSuccinctDescription_tapBackWithoutFinish()1828 public void testTapUrlSpanOnSuccinctDescription_tapBackWithoutFinish() throws Exception { 1829 saveUiRestoredAfterTappingSpanTest(DescriptionType.SUCCINCT, 1830 ViewActionActivity.ActivityCustomAction.TAP_BACK_WITHOUT_FINISH); 1831 } 1832 saveUiRestoredAfterTappingSpanTest( DescriptionType type, ViewActionActivity.ActivityCustomAction action)1833 private void saveUiRestoredAfterTappingSpanTest( 1834 DescriptionType type, ViewActionActivity.ActivityCustomAction action) throws Exception { 1835 startActivity(); 1836 // Set service. 1837 enableService(); 1838 1839 switch (type) { 1840 case SUCCINCT: 1841 // Set expectations with custom description. 1842 sReplier.addResponse(new CannedFillResponse.Builder() 1843 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT) 1844 .setSaveDescription(newDescriptionWithUrlSpan(action.toString())) 1845 .build()); 1846 break; 1847 case CUSTOM: 1848 // Set expectations with custom description. 1849 sReplier.addResponse(new CannedFillResponse.Builder() 1850 .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT) 1851 .setSaveInfoVisitor((contexts, builder) -> builder 1852 .setCustomDescription( 1853 newCustomDescriptionWithUrlSpan(action.toString()))) 1854 .build()); 1855 break; 1856 default: 1857 throw new IllegalArgumentException("invalid type: " + type); 1858 } 1859 1860 // Trigger autofill. 1861 mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus()); 1862 sReplier.getNextFillRequest(); 1863 1864 // Trigger save. 1865 mActivity.syncRunOnUiThread(() -> { 1866 mActivity.mInput.setText("108"); 1867 mActivity.mCommit.performClick(); 1868 }); 1869 // Waits for the commit be processed 1870 mUiBot.waitForIdle(); 1871 1872 mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC); 1873 1874 // Tapping URLSpan. 1875 final URLSpan span = mUiBot.findFirstUrlSpanWithText("Here is URLSpan"); 1876 mActivity.syncRunOnUiThread(() -> span.onClick(/* unused= */ null)); 1877 // Waits for the save UI hided 1878 mUiBot.waitForIdle(); 1879 1880 mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC); 1881 1882 // .. check activity show up as expected 1883 switch (action) { 1884 case FAST_FORWARD_ANOTHER_ACTIVITY: 1885 // Show up second activity. 1886 SecondActivity.assertShowingDefaultMessage(mUiBot); 1887 break; 1888 case NORMAL_ACTIVITY: 1889 case TAP_BACK_WITHOUT_FINISH: 1890 // Show up view action handle activity. 1891 ViewActionActivity.assertShowingDefaultMessage(mUiBot); 1892 break; 1893 default: 1894 throw new IllegalArgumentException("invalid action: " + action); 1895 } 1896 1897 // ..then go back and save it. 1898 mUiBot.pressBack(); 1899 // Waits for all UI processes to complete 1900 mUiBot.waitForIdle(); 1901 1902 // Make sure previous activity is back... 1903 mUiBot.assertShownByRelativeId(ID_INPUT); 1904 1905 // ... and tap save. 1906 final UiObject2 newSaveUi = mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC); 1907 mUiBot.saveForAutofill(newSaveUi, /* yesDoIt= */ true); 1908 1909 final SaveRequest saveRequest = sReplier.getNextSaveRequest(); 1910 assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_INPUT), "108"); 1911 1912 SecondActivity.finishIt(); 1913 ViewActionActivity.finishIt(); 1914 } 1915 newCustomDescriptionWithUrlSpan(String action)1916 private CustomDescription newCustomDescriptionWithUrlSpan(String action) { 1917 final RemoteViews presentation = newTemplate(); 1918 presentation.setTextViewText(R.id.custom_text, newDescriptionWithUrlSpan(action)); 1919 return new CustomDescription.Builder(presentation).build(); 1920 } 1921 newDescriptionWithUrlSpan(String action)1922 private CharSequence newDescriptionWithUrlSpan(String action) { 1923 final String url = "autofillcts:" + action; 1924 final SpannableString ss = new SpannableString("Here is URLSpan"); 1925 ss.setSpan(new URLSpan(url), 1926 /* start= */ 8, /* end= */ 15, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 1927 return ss; 1928 } 1929 } 1930