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