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