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