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.dropdown; 17 18 import static android.autofillservice.cts.activities.VirtualContainerView.ID_URL_BAR; 19 import static android.autofillservice.cts.activities.VirtualContainerView.LABEL_CLASS; 20 import static android.autofillservice.cts.activities.VirtualContainerView.TEXT_CLASS; 21 import static android.autofillservice.cts.testcore.CannedFillResponse.NO_RESPONSE; 22 import static android.autofillservice.cts.testcore.Helper.ID_PASSWORD; 23 import static android.autofillservice.cts.testcore.Helper.ID_PASSWORD_LABEL; 24 import static android.autofillservice.cts.testcore.Helper.ID_USERNAME; 25 import static android.autofillservice.cts.testcore.Helper.ID_USERNAME_LABEL; 26 import static android.autofillservice.cts.testcore.Helper.assertTextAndValue; 27 import static android.autofillservice.cts.testcore.Helper.assertTextIsSanitized; 28 import static android.autofillservice.cts.testcore.Helper.assertTextOnly; 29 import static android.autofillservice.cts.testcore.Helper.dumpStructure; 30 import static android.autofillservice.cts.testcore.Helper.findNodeByResourceId; 31 import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD; 32 33 import static com.google.common.truth.Truth.assertThat; 34 import static com.google.common.truth.Truth.assertWithMessage; 35 36 import static org.junit.Assume.assumeTrue; 37 38 import android.app.assist.AssistStructure.ViewNode; 39 import android.autofillservice.cts.activities.VirtualContainerActivity; 40 import android.autofillservice.cts.activities.VirtualContainerView; 41 import android.autofillservice.cts.activities.VirtualContainerView.Line; 42 import android.autofillservice.cts.activities.VirtualContainerView.VisibilityIntegrationMode; 43 import android.autofillservice.cts.commontests.AutoFillServiceTestCase; 44 import android.autofillservice.cts.testcore.AutofillActivityTestRule; 45 import android.autofillservice.cts.testcore.CannedFillResponse; 46 import android.autofillservice.cts.testcore.CannedFillResponse.CannedDataset; 47 import android.autofillservice.cts.testcore.Helper; 48 import android.autofillservice.cts.testcore.InstrumentedAutoFillService.FillRequest; 49 import android.autofillservice.cts.testcore.InstrumentedAutoFillService.SaveRequest; 50 import android.autofillservice.cts.testcore.MyAutofillCallback; 51 import android.content.AutofillOptions; 52 import android.graphics.Rect; 53 import android.platform.test.annotations.AppModeFull; 54 import android.platform.test.annotations.Presubmit; 55 import android.platform.test.annotations.RequiresFlagsDisabled; 56 import android.platform.test.annotations.RequiresFlagsEnabled; 57 import android.service.autofill.SaveInfo; 58 import android.text.InputType; 59 import android.view.ViewGroup; 60 import android.view.autofill.AutofillManager; 61 import android.view.flags.Flags; 62 63 import androidx.test.filters.FlakyTest; 64 import androidx.test.uiautomator.UiObject2; 65 66 import org.junit.Ignore; 67 import org.junit.Test; 68 69 import java.util.concurrent.TimeoutException; 70 71 /** 72 * Test case for an activity containing virtual children, either using the explicit Autofill APIs 73 * or Compat mode. 74 */ 75 public class VirtualContainerActivityTest 76 extends AutoFillServiceTestCase.AutoActivityLaunch<VirtualContainerActivity> { 77 78 // TODO(b/74256300): remove when fixed it :-) 79 private static final boolean BUG_74256300_FIXED = false; 80 81 private final boolean mCompatMode; 82 private AutofillActivityTestRule<VirtualContainerActivity> mActivityRule; 83 protected VirtualContainerActivity mActivity; 84 VirtualContainerActivityTest()85 public VirtualContainerActivityTest() { 86 this(false); 87 } 88 VirtualContainerActivityTest(boolean compatMode)89 protected VirtualContainerActivityTest(boolean compatMode) { 90 mCompatMode = compatMode; 91 } 92 93 /** 94 * Hook for subclass to customize test before activity is created. 95 */ preActivityCreated()96 protected void preActivityCreated() {} 97 98 /** 99 * Hook for subclass to customize activity after it's launched. 100 */ postActivityLaunched()101 protected void postActivityLaunched() {} 102 103 @Override getActivityRule()104 protected AutofillActivityTestRule<VirtualContainerActivity> getActivityRule() { 105 if (mActivityRule == null) { 106 mActivityRule = 107 new AutofillActivityTestRule<VirtualContainerActivity>( 108 VirtualContainerActivity.class) { 109 @Override 110 protected void beforeActivityLaunched() { 111 preActivityCreated(); 112 } 113 114 @Override 115 protected void afterActivityLaunched() { 116 mActivity = getActivity(); 117 mActivity.setAutofillOptions( 118 new AutofillOptions( 119 /*logginLevel=AutofillManager.NO_LOGGING*/ 120 0, mCompatMode)); 121 postActivityLaunched(); 122 } 123 }; 124 } 125 return mActivityRule; 126 } 127 128 @Presubmit 129 @Test testAutofillSync()130 public void testAutofillSync() throws Exception { 131 autofillTest(true); 132 } 133 134 @Test 135 @AppModeFull(reason = "testAutofillSync() is enough") testAutofillAsync()136 public void testAutofillAsync() throws Exception { 137 skipTestOnCompatMode(); 138 139 autofillTest(false); 140 } 141 142 @FlakyTest(bugId = 276895614) 143 @Test testAutofill_appContext()144 public void testAutofill_appContext() throws Exception { 145 mActivity.mCustomView.setAutofillManager(mActivity.getApplicationContext()); 146 autofillTest(true); 147 // Validation check to make sure autofill is enabled in the application context 148 assertThat(mActivity.getApplicationContext().getSystemService(AutofillManager.class) 149 .isEnabled()).isTrue(); 150 } 151 152 @Test 153 @RequiresFlagsEnabled(Flags.FLAG_CALCULATE_BOUNDS_IN_PARENT_FROM_BOUNDS_IN_SCREEN) testAutofill_calculateBoundsInParentFromBoundsInScreenFlagOn_BoundsSet()154 public void testAutofill_calculateBoundsInParentFromBoundsInScreenFlagOn_BoundsSet() 155 throws Exception { 156 autofillTest(false); 157 } 158 159 @Test 160 @RequiresFlagsDisabled(Flags.FLAG_CALCULATE_BOUNDS_IN_PARENT_FROM_BOUNDS_IN_SCREEN) testAutofill_calculateBoundsInParentFromBoundsInScreenFlagOff_BoundsSet()161 public void testAutofill_calculateBoundsInParentFromBoundsInScreenFlagOff_BoundsSet() 162 throws Exception { 163 autofillTest(false); 164 } 165 166 /** 167 * Focus to username and expect window event 168 */ focusToUsername()169 void focusToUsername() throws TimeoutException { 170 mUiBot.waitForWindowChange(() -> mActivity.mUsername.changeFocus(true)); 171 } 172 173 /** 174 * Focus to username and expect no autofill window event 175 */ focusToUsernameExpectNoWindowEvent()176 void focusToUsernameExpectNoWindowEvent() throws Throwable { 177 // TODO: should use waitForWindowChange() if we can filter out event of app Activity itself. 178 mActivityRule.runOnUiThread(() -> mActivity.mUsername.changeFocus(true)); 179 } 180 181 /** 182 * Focus to password and expect window event 183 */ focusToPassword()184 void focusToPassword() throws TimeoutException { 185 mUiBot.waitForWindowChange(() -> mActivity.mPassword.changeFocus(true)); 186 } 187 188 /** 189 * Focus to password and expect no autofill window event 190 */ focusToPasswordExpectNoWindowEvent()191 void focusToPasswordExpectNoWindowEvent() throws Throwable { 192 // TODO should use waitForWindowChange() if we can filter out event of app Activity itself. 193 mActivityRule.runOnUiThread(() -> mActivity.mPassword.changeFocus(true)); 194 } 195 196 /** 197 * Tests autofilling the virtual views, using the sync / async version of ViewStructure.addChild 198 */ autofillTest(boolean sync)199 private void autofillTest(boolean sync) throws Exception { 200 // Set service. 201 enableService(); 202 203 // Set expectations. 204 sReplier.addResponse(new CannedDataset.Builder() 205 .setField(ID_USERNAME, "dude", createPresentation("DUDE")) 206 .setField(ID_PASSWORD, "sweet", createPresentation("SWEET")) 207 .build()); 208 mActivity.expectAutoFill("dude", "sweet"); 209 mActivity.mCustomView.setSync(sync); 210 211 // Trigger auto-fill. 212 focusToUsername(); 213 assertDatasetShown(mActivity.mUsername, "DUDE"); 214 215 // Play around with focus to make sure picker is properly drawn. 216 if (BUG_74256300_FIXED || !mCompatMode) { 217 focusToPassword(); 218 assertDatasetShown(mActivity.mPassword, "SWEET"); 219 220 focusToUsername(); 221 assertDatasetShown(mActivity.mUsername, "DUDE"); 222 } 223 224 // Make sure input was sanitized. 225 final FillRequest request = sReplier.getNextFillRequest(); 226 final ViewNode urlBar = findNodeByResourceId(request.structure, ID_URL_BAR); 227 final ViewNode usernameLabel = findNodeByResourceId(request.structure, ID_USERNAME_LABEL); 228 final ViewNode username = findNodeByResourceId(request.structure, ID_USERNAME); 229 final ViewNode passwordLabel = findNodeByResourceId(request.structure, ID_PASSWORD_LABEL); 230 final ViewNode password = findNodeByResourceId(request.structure, ID_PASSWORD); 231 232 // Check bounds are set correctly 233 assertThat(request.structure.getWindowNodeCount()).isEqualTo(1); 234 assertBoundsSet(request.structure.getWindowNodeAt(0).getRootViewNode()); 235 236 assertUrlBarIsSanitized(urlBar); 237 assertTextIsSanitized(username); 238 assertTextIsSanitized(password); 239 assertLabel(usernameLabel, "Username"); 240 assertLabel(passwordLabel, "Password"); 241 242 assertThat(usernameLabel.getClassName()).isEqualTo(LABEL_CLASS); 243 assertThat(username.getClassName()).isEqualTo(TEXT_CLASS); 244 assertThat(passwordLabel.getClassName()).isEqualTo(LABEL_CLASS); 245 assertThat(password.getClassName()).isEqualTo(TEXT_CLASS); 246 247 assertThat(username.getIdEntry()).isEqualTo(ID_USERNAME); 248 assertThat(password.getIdEntry()).isEqualTo(ID_PASSWORD); 249 250 assertThat(username.getInputType()) 251 .isEqualTo(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_NORMAL); 252 assertThat(usernameLabel.getInputType()).isEqualTo(0); 253 assertThat(password.getInputType()) 254 .isEqualTo(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD); 255 assertThat(passwordLabel.getInputType()).isEqualTo(0); 256 257 final String[] autofillHints = username.getAutofillHints(); 258 final boolean hasCompatModeFlag = (request.flags 259 & android.service.autofill.FillRequest.FLAG_COMPATIBILITY_MODE_REQUEST) != 0; 260 if (mCompatMode) { 261 assertThat(hasCompatModeFlag).isTrue(); 262 assertThat(autofillHints).isNull(); 263 assertThat(username.getHtmlInfo()).isNull(); 264 assertThat(password.getHtmlInfo()).isNull(); 265 } else { 266 assertThat(hasCompatModeFlag).isFalse(); 267 // Make sure order is preserved and dupes not removed. 268 assertThat(autofillHints).asList() 269 .containsExactly("c", "a", "a", "b", "a", "a") 270 .inOrder(); 271 try { 272 VirtualContainerView.assertHtmlInfo(username); 273 VirtualContainerView.assertHtmlInfo(password); 274 } catch (AssertionError | RuntimeException e) { 275 dumpStructure("HtmlInfo failed", request.structure); 276 throw e; 277 } 278 } 279 280 // Make sure initial focus was properly set. 281 assertWithMessage("Username node is not focused").that(username.isFocused()).isTrue(); 282 assertWithMessage("Password node is focused").that(password.isFocused()).isFalse(); 283 284 // Auto-fill it. 285 mUiBot.selectDataset("DUDE"); 286 287 // Check the results. 288 mActivity.assertAutoFilled(); 289 } 290 assertBoundsSet(ViewNode viewNode)291 private void assertBoundsSet(ViewNode viewNode) { 292 String idEntry = viewNode.getIdEntry(); 293 // Values come from 294 // tests/autofillservice/res/layout/virtual_container_activity.xml 295 // The left and top values come from the margin 296 if (idEntry != null && idEntry.equals("text_view_child")) { 297 assertThat(viewNode.getLeft()).isEqualTo(10); 298 assertThat(viewNode.getTop()).isEqualTo(10); 299 assertThat(viewNode.getWidth()).isEqualTo(50); 300 assertThat(viewNode.getHeight()).isEqualTo(60); 301 } 302 // Recursively search for the ViewNode 303 for (int i = 0; i < viewNode.getChildCount(); ++i) { 304 assertBoundsSet(viewNode.getChildAt(i)); 305 } 306 } 307 308 @Test 309 @AppModeFull(reason = "testAutofillSync() is enough") testAutofillTwoDatasets()310 public void testAutofillTwoDatasets() throws Exception { 311 // Set service. 312 enableService(); 313 314 // Set expectations. 315 sReplier.addResponse(new CannedFillResponse.Builder() 316 .addDataset(new CannedDataset.Builder() 317 .setField(ID_USERNAME, "dude") 318 .setField(ID_PASSWORD, "sweet") 319 .setPresentation(createPresentation("The Dude")) 320 .build()) 321 .addDataset(new CannedDataset.Builder() 322 .setField(ID_USERNAME, "DUDE") 323 .setField(ID_PASSWORD, "SWEET") 324 .setPresentation(createPresentation("THE DUDE")) 325 .build()) 326 .build()); 327 mActivity.expectAutoFill("DUDE", "SWEET"); 328 329 // Trigger auto-fill. 330 focusToUsername(); 331 sReplier.getNextFillRequest(); 332 assertDatasetShown(mActivity.mUsername, "The Dude", "THE DUDE"); 333 334 // Play around with focus to make sure picker is properly drawn. 335 if (BUG_74256300_FIXED || !mCompatMode) { 336 focusToPassword(); 337 assertDatasetShown(mActivity.mPassword, "The Dude", "THE DUDE"); 338 focusToUsername(); 339 assertDatasetShown(mActivity.mUsername, "The Dude", "THE DUDE"); 340 } 341 342 // Auto-fill it. 343 mUiBot.selectDataset("THE DUDE"); 344 345 // Check the results. 346 mActivity.assertAutoFilled(); 347 } 348 349 @Presubmit 350 @Test testAutofillOverrideDispatchProvideAutofillStructure()351 public void testAutofillOverrideDispatchProvideAutofillStructure() throws Exception { 352 mActivity.mCustomView.setOverrideDispatchProvideAutofillStructure(true); 353 autofillTest(true); 354 } 355 356 @Presubmit 357 @Test testAutofillManuallyOneDataset()358 public void testAutofillManuallyOneDataset() throws Exception { 359 skipTestOnCompatMode(); // TODO(b/73557072): not supported yet 360 361 // Set service. 362 enableService(); 363 364 // Set expectations. 365 sReplier.addResponse(new CannedDataset.Builder() 366 .setField(ID_USERNAME, "dude") 367 .setField(ID_PASSWORD, "sweet") 368 .setPresentation(createPresentation("The Dude")) 369 .build()); 370 mActivity.expectAutoFill("dude", "sweet"); 371 372 // Trigger auto-fill. 373 mActivity.requestAutofill(mActivity.mUsername); 374 sReplier.getNextFillRequest(); 375 376 // Select datatest. 377 mUiBot.selectDataset("The Dude"); 378 379 // Check the results. 380 mActivity.assertAutoFilled(); 381 } 382 383 @Test 384 @AppModeFull(reason = "testAutofillManuallyOneDataset() is enough") testAutofillManuallyTwoDatasetsPickFirst()385 public void testAutofillManuallyTwoDatasetsPickFirst() throws Exception { 386 autofillManuallyTwoDatasets(true); 387 } 388 389 @Test 390 @AppModeFull(reason = "testAutofillManuallyOneDataset() is enough") testAutofillManuallyTwoDatasetsPickSecond()391 public void testAutofillManuallyTwoDatasetsPickSecond() throws Exception { 392 autofillManuallyTwoDatasets(false); 393 } 394 autofillManuallyTwoDatasets(boolean pickFirst)395 private void autofillManuallyTwoDatasets(boolean pickFirst) throws Exception { 396 skipTestOnCompatMode(); // TODO(b/73557072): not supported yet 397 398 // Set service. 399 enableService(); 400 401 // Set expectations. 402 sReplier.addResponse(new CannedFillResponse.Builder() 403 .addDataset(new CannedDataset.Builder() 404 .setField(ID_USERNAME, "dude") 405 .setField(ID_PASSWORD, "sweet") 406 .setPresentation(createPresentation("The Dude")) 407 .build()) 408 .addDataset(new CannedDataset.Builder() 409 .setField(ID_USERNAME, "jenny") 410 .setField(ID_PASSWORD, "8675309") 411 .setPresentation(createPresentation("Jenny")) 412 .build()) 413 .build()); 414 if (pickFirst) { 415 mActivity.expectAutoFill("dude", "sweet"); 416 } else { 417 mActivity.expectAutoFill("jenny", "8675309"); 418 419 } 420 421 // Trigger auto-fill. 422 mActivity.getSystemService(AutofillManager.class).requestAutofill( 423 mActivity.mCustomView, mActivity.mUsername.text.id, 424 mActivity.mUsername.getAbsCoordinates()); 425 sReplier.getNextFillRequest(); 426 427 // Auto-fill it. 428 final UiObject2 picker = assertDatasetShown(mActivity.mUsername, "The Dude", "Jenny"); 429 mUiBot.selectDataset(picker, pickFirst ? "The Dude" : "Jenny"); 430 431 // Check the results. 432 mActivity.assertAutoFilled(); 433 } 434 435 @Presubmit 436 @Test testAutofillCallbacks()437 public void testAutofillCallbacks() throws Exception { 438 // Set service. 439 enableService(); 440 final MyAutofillCallback callback = mActivity.registerCallback(); 441 442 // Set expectations. 443 sReplier.addResponse(new CannedDataset.Builder() 444 .setField(ID_USERNAME, "dude") 445 .setField(ID_PASSWORD, "sweet") 446 .setPresentation(createPresentation("The Dude")) 447 .build()); 448 mActivity.expectAutoFill("dude", "sweet"); 449 450 // Trigger auto-fill. 451 focusToUsername(); 452 sReplier.getNextFillRequest(); 453 454 callback.assertUiShownEvent(mActivity.mCustomView, mActivity.mUsername.text.id); 455 456 // Change focus 457 focusToPassword(); 458 callback.assertUiHiddenEvent(mActivity.mCustomView, mActivity.mUsername.text.id); 459 callback.assertUiShownEvent(mActivity.mCustomView, mActivity.mPassword.text.id); 460 } 461 462 @Test 463 @AppModeFull(reason = "testAutofillCallbacks() is enough") testAutofillCallbackDisabled()464 public void testAutofillCallbackDisabled() throws Throwable { 465 // Set service. 466 disableService(); 467 final MyAutofillCallback callback = mActivity.registerCallback(); 468 469 // Trigger auto-fill. 470 focusToUsernameExpectNoWindowEvent(); 471 472 // Assert callback was called 473 callback.assertUiUnavailableEvent(mActivity.mCustomView, mActivity.mUsername.text.id); 474 } 475 476 @Test 477 @AppModeFull(reason = "testAutofillCallbacks() is enough") testAutofillCallbackNoDatasets()478 public void testAutofillCallbackNoDatasets() throws Throwable { 479 // Set service. 480 enableService(); 481 final MyAutofillCallback callback = mActivity.registerCallback(); 482 483 // Set expectations. 484 sReplier.addResponse(NO_RESPONSE); 485 486 // Trigger autofill. 487 focusToUsernameExpectNoWindowEvent(); 488 sReplier.getNextFillRequest(); 489 490 // Auto-fill it. 491 mUiBot.assertNoDatasetsEver(); 492 493 // Assert callback was called 494 callback.assertUiUnavailableEvent(mActivity.mCustomView, mActivity.mUsername.text.id); 495 } 496 497 @Test 498 @AppModeFull(reason = "testAutofillCallbacks() is enough") testAutofillCallbackNoDatasetsButSaveInfo()499 public void testAutofillCallbackNoDatasetsButSaveInfo() throws Throwable { 500 // Set service. 501 enableService(); 502 final MyAutofillCallback callback = mActivity.registerCallback(); 503 504 // Set expectations. 505 sReplier.addResponse(new CannedFillResponse.Builder() 506 .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD) 507 .build()); 508 509 // Trigger autofill. 510 focusToUsernameExpectNoWindowEvent(); 511 sReplier.getNextFillRequest(); 512 513 // Autofill it. 514 mUiBot.assertNoDatasetsEver(); 515 516 // Assert callback was called 517 callback.assertUiUnavailableEvent(mActivity.mCustomView, mActivity.mUsername.text.id); 518 519 // Make sure save is not triggered 520 mActivity.getAutofillManager().commit(); 521 mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD); 522 } 523 524 @Presubmit 525 @Test testSaveDialogNotShownWhenBackIsPressed()526 public void testSaveDialogNotShownWhenBackIsPressed() throws Exception { 527 // Set service. 528 enableService(); 529 530 // Set expectations. 531 sReplier.addResponse(new CannedFillResponse.Builder() 532 .addDataset(new CannedDataset.Builder() 533 .setField(ID_USERNAME, "dude") 534 .setField(ID_PASSWORD, "sweet") 535 .setPresentation(createPresentation("The Dude")) 536 .build()) 537 .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD) 538 .build()); 539 mActivity.expectAutoFill("dude", "sweet"); 540 541 // Trigger auto-fill. 542 focusToUsername(); 543 sReplier.getNextFillRequest(); 544 assertDatasetShown(mActivity.mUsername, "The Dude"); 545 546 mUiBot.pressBack(); 547 548 mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD); 549 } 550 551 @Presubmit 552 @Test testSave_childViewsGone_notifyAfm()553 public void testSave_childViewsGone_notifyAfm() throws Throwable { 554 saveTest(CommitType.CHILDREN_VIEWS_GONE_NOTIFY_CALLBACK_API); 555 } 556 557 @Presubmit 558 @Test testSave_childViewsGone_updateView()559 public void testSave_childViewsGone_updateView() throws Throwable { 560 saveTest(CommitType.CHILDREN_VIEWS_GONE_NOTIFY_CALLBACK_API); 561 } 562 563 @Test 564 @Ignore("Disabled until b/73493342 is fixed") testSave_parentViewGone()565 public void testSave_parentViewGone() throws Throwable { 566 saveTest(CommitType.PARENT_VIEW_GONE); 567 } 568 569 @Presubmit 570 @Test testSave_appCallsCommit()571 public void testSave_appCallsCommit() throws Throwable { 572 saveTest(CommitType.EXPLICIT_COMMIT); 573 } 574 575 @Presubmit 576 @Test testSave_submitButtonClicked()577 public void testSave_submitButtonClicked() throws Throwable { 578 saveTest(CommitType.SUBMIT_BUTTON_CLICKED); 579 } 580 581 enum CommitType { 582 CHILDREN_VIEWS_GONE_NOTIFY_CALLBACK_API, 583 CHILDREN_VIEWS_GONE_IS_VISIBLE_API, 584 PARENT_VIEW_GONE, 585 EXPLICIT_COMMIT, 586 SUBMIT_BUTTON_CLICKED 587 } 588 saveTest(CommitType commitType)589 private void saveTest(CommitType commitType) throws Throwable { 590 // Set service. 591 enableService(); 592 593 // Set expectations. 594 final CannedFillResponse.Builder response = new CannedFillResponse.Builder() 595 .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD); 596 597 switch (commitType) { 598 case CHILDREN_VIEWS_GONE_NOTIFY_CALLBACK_API: 599 case CHILDREN_VIEWS_GONE_IS_VISIBLE_API: 600 case PARENT_VIEW_GONE: 601 response.setSaveInfoFlags(SaveInfo.FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE); 602 break; 603 case EXPLICIT_COMMIT: 604 // does nothing 605 break; 606 case SUBMIT_BUTTON_CLICKED: 607 response 608 .setSaveInfoFlags(SaveInfo.FLAG_DONT_SAVE_ON_FINISH) 609 .setSaveTriggerId(mActivity.mCustomView.mLoginButtonId); 610 break; 611 default: 612 throw new IllegalArgumentException("invalid type: " + commitType); 613 } 614 sReplier.addResponse(response.build()); 615 616 // Trigger auto-fill. 617 focusToUsernameExpectNoWindowEvent(); 618 sReplier.getNextFillRequest(); 619 // Fill in some stuff 620 mActivity.mUsername.setText("foo"); 621 622 // Add a delay to prevent fill request triggers when focusing on 623 // password field 624 mUiBot.waitForIdleSync(); 625 626 focusToPasswordExpectNoWindowEvent(); 627 mActivity.mPassword.setText("bar"); 628 629 // Add a delay to prevent save trigger happens too fast 630 mUiBot.waitForIdleSync(); 631 632 // Trigger save. 633 switch (commitType) { 634 case CHILDREN_VIEWS_GONE_NOTIFY_CALLBACK_API: 635 setViewsInvisible(VisibilityIntegrationMode.NOTIFY_AFM); 636 break; 637 case CHILDREN_VIEWS_GONE_IS_VISIBLE_API: 638 setViewsInvisible(VisibilityIntegrationMode.OVERRIDE_IS_VISIBLE_TO_USER); 639 break; 640 case PARENT_VIEW_GONE: 641 mActivity.runOnUiThread(() -> { 642 final ViewGroup parent = (ViewGroup) mActivity.mCustomView.getParent(); 643 parent.removeView(mActivity.mCustomView); 644 }); 645 break; 646 case EXPLICIT_COMMIT: 647 mActivity.getAutofillManager().commit(); 648 break; 649 case SUBMIT_BUTTON_CLICKED: 650 mActivity.mCustomView.clickLogin(); 651 break; 652 default: 653 throw new IllegalArgumentException("unknown type: " + commitType); 654 } 655 656 // Assert UI is showing. 657 mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD); 658 659 // Assert results 660 final SaveRequest saveRequest = sReplier.getNextSaveRequest(); 661 final ViewNode username = findNodeByResourceId(saveRequest.structure, ID_USERNAME); 662 final ViewNode password = findNodeByResourceId(saveRequest.structure, ID_PASSWORD); 663 664 assertTextAndValue(username, "foo"); 665 assertTextAndValue(password, "bar"); 666 } 667 setViewsInvisible(VisibilityIntegrationMode mode)668 protected void setViewsInvisible(VisibilityIntegrationMode mode) { 669 mActivity.mUsername.setVisibilityIntegrationMode(mode); 670 mActivity.mPassword.setVisibilityIntegrationMode(mode); 671 mActivity.mUsername.changeVisibility(false); 672 mActivity.mPassword.changeVisibility(false); 673 } 674 675 // NOTE: tests where save is not shown only makes sense when calling commit() explicitly, 676 // otherwise the test could pass but the UI is still shown *after* the app is committed. 677 // We could still test them by explicitly committing and then checking that the Save UI is not 678 // shown again, but then we wouldn't be effectively testing that the context was committed 679 680 @Presubmit 681 @Test testSaveNotShown_noUserInput()682 public void testSaveNotShown_noUserInput() throws Throwable { 683 // Set service. 684 enableService(); 685 686 // Set expectations. 687 sReplier.addResponse(new CannedFillResponse.Builder() 688 .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD).build()); 689 690 // Trigger auto-fill. 691 focusToUsernameExpectNoWindowEvent(); 692 sReplier.getNextFillRequest(); 693 694 // Trigger save. 695 mActivity.getAutofillManager().commit(); 696 697 // Assert it's not showing. 698 mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD); 699 } 700 701 @Test 702 @AppModeFull(reason = "testSaveNotShown_noUserInput() is enough") testSaveNotShown_initialValues_noUserInput()703 public void testSaveNotShown_initialValues_noUserInput() throws Throwable { 704 // Prepare activitiy. 705 mActivity.mUsername.setText("foo"); 706 mActivity.mPassword.setText("bar"); 707 708 // Set service. 709 enableService(); 710 711 // Set expectations. 712 sReplier.addResponse(new CannedFillResponse.Builder() 713 .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD).build()); 714 715 // Trigger auto-fill. 716 focusToUsernameExpectNoWindowEvent(); 717 sReplier.getNextFillRequest(); 718 719 // Trigger save. 720 mActivity.getAutofillManager().commit(); 721 722 // Assert it's not showing. 723 mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD); 724 } 725 726 @Test 727 @AppModeFull(reason = "testSaveNotShown_noUserInput() is enough") testSaveNotShown_initialValues_noUserInput_serviceDatasets()728 public void testSaveNotShown_initialValues_noUserInput_serviceDatasets() throws Throwable { 729 // Prepare activitiy. 730 mActivity.mUsername.setText("foo"); 731 mActivity.mPassword.setText("bar"); 732 733 // Set service. 734 enableService(); 735 736 // Set expectations. 737 sReplier.addResponse(new CannedFillResponse.Builder() 738 .addDataset(new CannedDataset.Builder() 739 .setField(ID_USERNAME, "dude") 740 .setField(ID_PASSWORD, "sweet") 741 .setPresentation(createPresentation("The Dude")) 742 .build()) 743 .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD).build()); 744 745 // Trigger auto-fill. 746 focusToUsernameExpectNoWindowEvent(); 747 sReplier.getNextFillRequest(); 748 assertDatasetShown(mActivity.mUsername, "The Dude"); 749 750 // Trigger save. 751 mActivity.getAutofillManager().commit(); 752 753 // Assert it's not showing. 754 mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD); 755 } 756 757 @Test 758 @AppModeFull(reason = "testSaveNotShown_noUserInput() is enough") testSaveNotShown_userInputMatchesDatasets()759 public void testSaveNotShown_userInputMatchesDatasets() throws Throwable { 760 // Prepare activitiy. 761 mActivity.mUsername.setText("foo"); 762 mActivity.mPassword.setText("bar"); 763 764 // Set service. 765 enableService(); 766 767 // Set expectations. 768 sReplier.addResponse(new CannedFillResponse.Builder() 769 .addDataset(new CannedDataset.Builder() 770 .setField(ID_USERNAME, "foo") 771 .setField(ID_PASSWORD, "bar") 772 .setPresentation(createPresentation("The Dude")) 773 .build()) 774 .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD).build()); 775 776 // Trigger auto-fill. 777 focusToUsernameExpectNoWindowEvent(); 778 sReplier.getNextFillRequest(); 779 assertDatasetShown(mActivity.mUsername, "The Dude"); 780 781 // Trigger save. 782 mActivity.getAutofillManager().commit(); 783 784 // Assert it's not showing. 785 mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD); 786 } 787 788 @Presubmit 789 @Test testDatasetFiltering()790 public void testDatasetFiltering() throws Throwable { 791 final String aa = "Two A's"; 792 final String ab = "A and B"; 793 final String b = "Only B"; 794 795 enableService(); 796 797 // Set expectations. 798 sReplier.addResponse(new CannedFillResponse.Builder() 799 .addDataset(new CannedDataset.Builder() 800 .setField(ID_USERNAME, "aa") 801 .setPresentation(createPresentation(aa)) 802 .build()) 803 .addDataset(new CannedDataset.Builder() 804 .setField(ID_USERNAME, "ab") 805 .setPresentation(createPresentation(ab)) 806 .build()) 807 .addDataset(new CannedDataset.Builder() 808 .setField(ID_USERNAME, "b") 809 .setPresentation(createPresentation(b)) 810 .build()) 811 .build()); 812 813 // Trigger auto-fill. 814 focusToUsernameExpectNoWindowEvent(); 815 sReplier.getNextFillRequest(); 816 817 // With no filter text all datasets should be shown 818 assertDatasetShown(mActivity.mUsername, aa, ab, b); 819 820 // Only two datasets start with 'a' 821 mActivity.mUsername.setText("a"); 822 assertDatasetShown(mActivity.mUsername, aa, ab); 823 824 // Only one dataset start with 'aa' 825 mActivity.mUsername.setText("aa"); 826 assertDatasetShown(mActivity.mUsername, aa); 827 828 // Only two datasets start with 'a' 829 mActivity.mUsername.setText("a"); 830 assertDatasetShown(mActivity.mUsername, aa, ab); 831 832 // With no filter text all datasets should be shown 833 mActivity.mUsername.setText(""); 834 assertDatasetShown(mActivity.mUsername, aa, ab, b); 835 836 // No dataset start with 'aaa' 837 final MyAutofillCallback callback = mActivity.registerCallback(); 838 mActivity.mUsername.setText("aaa"); 839 callback.assertUiHiddenEvent(mActivity.mCustomView, mActivity.mUsername.text.id); 840 mUiBot.assertNoDatasets(); 841 } 842 843 /** 844 * Asserts the dataset picker is properly displayed in a give line. 845 */ assertDatasetShown(Line line, String... expectedDatasets)846 protected UiObject2 assertDatasetShown(Line line, String... expectedDatasets) 847 throws Exception { 848 boolean autofillViewBoundsMatches = !Helper.isAutofillWindowFullScreen(mContext); 849 final UiObject2 datasetPicker = mUiBot.assertDatasets(expectedDatasets); 850 final Rect pickerBounds = datasetPicker.getVisibleBounds(); 851 final Rect fieldBounds = line.getAbsCoordinates(); 852 if (autofillViewBoundsMatches) { 853 assertWithMessage("vertical coordinates don't match; picker=%s, field=%s", pickerBounds, 854 fieldBounds).that(pickerBounds.top).isEqualTo(fieldBounds.bottom); 855 assertWithMessage("horizontal coordinates don't match; picker=%s, field=%s", 856 pickerBounds, fieldBounds).that(pickerBounds.left).isEqualTo(fieldBounds.left); 857 } 858 return datasetPicker; 859 } 860 assertLabel(ViewNode node, String expectedValue)861 protected void assertLabel(ViewNode node, String expectedValue) { 862 if (mCompatMode) { 863 // Compat mode doesn't set AutofillValue of non-editable fields 864 assertTextOnly(node, expectedValue); 865 } else { 866 assertTextAndValue(node, expectedValue); 867 } 868 } 869 assertUrlBarIsSanitized(ViewNode urlBar)870 protected void assertUrlBarIsSanitized(ViewNode urlBar) { 871 assertTextIsSanitized(urlBar); 872 assertThat(urlBar.getWebDomain()).isNull(); 873 assertThat(urlBar.getWebScheme()).isNull(); 874 } 875 876 skipTestOnCompatMode()877 private void skipTestOnCompatMode() { 878 assumeTrue("test not applicable when on compat mode", !mCompatMode); 879 } 880 } 881