1 /* 2 * Copyright (C) 2018 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 17 package android.autofillservice.cts; 18 19 import static android.autofillservice.cts.Helper.ID_USERNAME; 20 import static android.autofillservice.cts.Timeouts.MOCK_IME_TIMEOUT_MS; 21 22 import static com.android.compatibility.common.util.ShellUtils.sendKeyEvent; 23 import static com.android.cts.mockime.ImeEventStreamTestUtils.editorMatcher; 24 import static com.android.cts.mockime.ImeEventStreamTestUtils.expectBindInput; 25 import static com.android.cts.mockime.ImeEventStreamTestUtils.expectCommand; 26 import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEvent; 27 28 import android.autofillservice.cts.CannedFillResponse.CannedDataset; 29 import android.content.IntentSender; 30 import android.os.Process; 31 import android.platform.test.annotations.AppModeFull; 32 import android.view.KeyEvent; 33 import android.view.View; 34 import android.widget.EditText; 35 36 import com.android.cts.mockime.ImeCommand; 37 import com.android.cts.mockime.ImeEventStream; 38 import com.android.cts.mockime.MockImeSession; 39 40 import org.junit.AfterClass; 41 import org.junit.BeforeClass; 42 import org.junit.Test; 43 44 import java.util.regex.Pattern; 45 46 public class DatasetFilteringTest extends AbstractLoginActivityTestCase { 47 48 @BeforeClass setMaxDatasets()49 public static void setMaxDatasets() throws Exception { 50 Helper.setMaxVisibleDatasets(4); 51 } 52 53 @AfterClass restoreMaxDatasets()54 public static void restoreMaxDatasets() throws Exception { 55 Helper.setMaxVisibleDatasets(0); 56 } 57 changeUsername(CharSequence username)58 private void changeUsername(CharSequence username) { 59 mActivity.onUsername((v) -> v.setText(username)); 60 } 61 62 63 @Test testFilter()64 public void testFilter() throws Exception { 65 final String aa = "Two A's"; 66 final String ab = "A and B"; 67 final String b = "Only B"; 68 69 enableService(); 70 71 // Set expectations. 72 sReplier.addResponse(new CannedFillResponse.Builder() 73 .addDataset(new CannedDataset.Builder() 74 .setField(ID_USERNAME, "aa") 75 .setPresentation(createPresentation(aa)) 76 .build()) 77 .addDataset(new CannedDataset.Builder() 78 .setField(ID_USERNAME, "ab") 79 .setPresentation(createPresentation(ab)) 80 .build()) 81 .addDataset(new CannedDataset.Builder() 82 .setField(ID_USERNAME, "b") 83 .setPresentation(createPresentation(b)) 84 .build()) 85 .build()); 86 87 // Trigger auto-fill. 88 requestFocusOnUsername(); 89 sReplier.getNextFillRequest(); 90 91 // With no filter text all datasets should be shown 92 mUiBot.assertDatasets(aa, ab, b); 93 94 // Only two datasets start with 'a' 95 changeUsername("a"); 96 mUiBot.assertDatasets(aa, ab); 97 98 // Only one dataset start with 'aa' 99 changeUsername("aa"); 100 mUiBot.assertDatasets(aa); 101 102 // Only two datasets start with 'a' 103 changeUsername("a"); 104 mUiBot.assertDatasets(aa, ab); 105 106 // With no filter text all datasets should be shown 107 changeUsername(""); 108 mUiBot.assertDatasets(aa, ab, b); 109 110 // No dataset start with 'aaa' 111 final MyAutofillCallback callback = mActivity.registerCallback(); 112 changeUsername("aaa"); 113 callback.assertUiHiddenEvent(mActivity.getUsername()); 114 mUiBot.assertNoDatasets(); 115 } 116 117 @Test testFilter_injectingEvents()118 public void testFilter_injectingEvents() throws Exception { 119 final String aa = "Two A's"; 120 final String ab = "A and B"; 121 final String b = "Only B"; 122 123 enableService(); 124 125 // Set expectations. 126 sReplier.addResponse(new CannedFillResponse.Builder() 127 .addDataset(new CannedDataset.Builder() 128 .setField(ID_USERNAME, "aa") 129 .setPresentation(createPresentation(aa)) 130 .build()) 131 .addDataset(new CannedDataset.Builder() 132 .setField(ID_USERNAME, "ab") 133 .setPresentation(createPresentation(ab)) 134 .build()) 135 .addDataset(new CannedDataset.Builder() 136 .setField(ID_USERNAME, "b") 137 .setPresentation(createPresentation(b)) 138 .build()) 139 .build()); 140 141 // Trigger auto-fill. 142 requestFocusOnUsername(); 143 sReplier.getNextFillRequest(); 144 145 // With no filter text all datasets should be shown 146 mUiBot.assertDatasets(aa, ab, b); 147 148 // Only two datasets start with 'a' 149 sendKeyEvent("KEYCODE_A"); 150 mUiBot.assertDatasets(aa, ab); 151 152 // Only one dataset start with 'aa' 153 sendKeyEvent("KEYCODE_A"); 154 mUiBot.assertDatasets(aa); 155 156 // Only two datasets start with 'a' 157 sendKeyEvent("KEYCODE_DEL"); 158 mUiBot.assertDatasets(aa, ab); 159 160 // With no filter text all datasets should be shown 161 sendKeyEvent("KEYCODE_DEL"); 162 mUiBot.assertDatasets(aa, ab, b); 163 164 // No dataset start with 'aaa' 165 final MyAutofillCallback callback = mActivity.registerCallback(); 166 sendKeyEvent("KEYCODE_A"); 167 sendKeyEvent("KEYCODE_A"); 168 sendKeyEvent("KEYCODE_A"); 169 callback.assertUiHiddenEvent(mActivity.getUsername()); 170 mUiBot.assertNoDatasets(); 171 } 172 173 @Test testFilter_usingKeyboard()174 public void testFilter_usingKeyboard() throws Exception { 175 final String aa = "Two A's"; 176 final String ab = "A and B"; 177 final String b = "Only B"; 178 179 final MockImeSession mockImeSession = sMockImeSessionRule.getMockImeSession(); 180 181 enableService(); 182 183 // Set expectations. 184 sReplier.addResponse(new CannedFillResponse.Builder() 185 .addDataset(new CannedDataset.Builder() 186 .setField(ID_USERNAME, "aa") 187 .setPresentation(createPresentation(aa)) 188 .build()) 189 .addDataset(new CannedDataset.Builder() 190 .setField(ID_USERNAME, "ab") 191 .setPresentation(createPresentation(ab)) 192 .build()) 193 .addDataset(new CannedDataset.Builder() 194 .setField(ID_USERNAME, "b") 195 .setPresentation(createPresentation(b)) 196 .build()) 197 .build()); 198 199 final ImeEventStream stream = mockImeSession.openEventStream(); 200 201 // Trigger auto-fill. 202 mActivity.onUsername(View::requestFocus); 203 204 // Wait until the MockIme gets bound to the TestActivity. 205 expectBindInput(stream, Process.myPid(), MOCK_IME_TIMEOUT_MS); 206 expectEvent(stream, editorMatcher("onStartInput", mActivity.getUsername().getId()), 207 MOCK_IME_TIMEOUT_MS); 208 209 sReplier.getNextFillRequest(); 210 211 // With no filter text all datasets should be shown 212 mUiBot.assertDatasets(aa, ab, b); 213 214 // Only two datasets start with 'a' 215 final ImeCommand cmd1 = mockImeSession.callCommitText("a", 1); 216 expectCommand(stream, cmd1, MOCK_IME_TIMEOUT_MS); 217 mUiBot.assertDatasets(aa, ab); 218 219 // Only one dataset start with 'aa' 220 final ImeCommand cmd2 = mockImeSession.callCommitText("a", 1); 221 expectCommand(stream, cmd2, MOCK_IME_TIMEOUT_MS); 222 mUiBot.assertDatasets(aa); 223 224 // Only two datasets start with 'a' 225 final ImeCommand cmd3 = mockImeSession.callSendDownUpKeyEvents(KeyEvent.KEYCODE_DEL); 226 expectCommand(stream, cmd3, MOCK_IME_TIMEOUT_MS); 227 mUiBot.assertDatasets(aa, ab); 228 229 // With no filter text all datasets should be shown 230 final ImeCommand cmd4 = mockImeSession.callSendDownUpKeyEvents(KeyEvent.KEYCODE_DEL); 231 expectCommand(stream, cmd4, MOCK_IME_TIMEOUT_MS); 232 mUiBot.assertDatasets(aa, ab, b); 233 234 // No dataset start with 'aaa' 235 final MyAutofillCallback callback = mActivity.registerCallback(); 236 final ImeCommand cmd5 = mockImeSession.callCommitText("aaa", 1); 237 expectCommand(stream, cmd5, MOCK_IME_TIMEOUT_MS); 238 callback.assertUiHiddenEvent(mActivity.getUsername()); 239 mUiBot.assertNoDatasets(); 240 } 241 242 @Test 243 @AppModeFull(reason = "testFilter() is enough") testFilter_nullValuesAlwaysMatched()244 public void testFilter_nullValuesAlwaysMatched() throws Exception { 245 final String aa = "Two A's"; 246 final String ab = "A and B"; 247 final String b = "Only B"; 248 249 enableService(); 250 251 // Set expectations. 252 sReplier.addResponse(new CannedFillResponse.Builder() 253 .addDataset(new CannedDataset.Builder() 254 .setField(ID_USERNAME, "aa") 255 .setPresentation(createPresentation(aa)) 256 .build()) 257 .addDataset(new CannedDataset.Builder() 258 .setField(ID_USERNAME, "ab") 259 .setPresentation(createPresentation(ab)) 260 .build()) 261 .addDataset(new CannedDataset.Builder() 262 .setField(ID_USERNAME, (String) null) 263 .setPresentation(createPresentation(b)) 264 .build()) 265 .build()); 266 267 // Trigger auto-fill. 268 requestFocusOnUsername(); 269 sReplier.getNextFillRequest(); 270 271 // With no filter text all datasets should be shown 272 mUiBot.assertDatasets(aa, ab, b); 273 274 // Two datasets start with 'a' and one with null value always shown 275 changeUsername("a"); 276 mUiBot.assertDatasets(aa, ab, b); 277 278 // One dataset start with 'aa' and one with null value always shown 279 changeUsername("aa"); 280 mUiBot.assertDatasets(aa, b); 281 282 // Two datasets start with 'a' and one with null value always shown 283 changeUsername("a"); 284 mUiBot.assertDatasets(aa, ab, b); 285 286 // With no filter text all datasets should be shown 287 changeUsername(""); 288 mUiBot.assertDatasets(aa, ab, b); 289 290 // No dataset start with 'aaa' and one with null value always shown 291 changeUsername("aaa"); 292 mUiBot.assertDatasets(b); 293 } 294 295 @Test 296 @AppModeFull(reason = "testFilter() is enough") testFilter_differentPrefixes()297 public void testFilter_differentPrefixes() throws Exception { 298 final String a = "aaa"; 299 final String b = "bra"; 300 final String c = "cadabra"; 301 302 enableService(); 303 304 // Set expectations. 305 sReplier.addResponse(new CannedFillResponse.Builder() 306 .addDataset(new CannedDataset.Builder() 307 .setField(ID_USERNAME, a) 308 .setPresentation(createPresentation(a)) 309 .build()) 310 .addDataset(new CannedDataset.Builder() 311 .setField(ID_USERNAME, b) 312 .setPresentation(createPresentation(b)) 313 .build()) 314 .addDataset(new CannedDataset.Builder() 315 .setField(ID_USERNAME, c) 316 .setPresentation(createPresentation(c)) 317 .build()) 318 .build()); 319 320 // Trigger auto-fill. 321 requestFocusOnUsername(); 322 sReplier.getNextFillRequest(); 323 324 // With no filter text all datasets should be shown 325 mUiBot.assertDatasets(a, b, c); 326 327 changeUsername("a"); 328 mUiBot.assertDatasets(a); 329 330 changeUsername("b"); 331 mUiBot.assertDatasets(b); 332 333 changeUsername("c"); 334 mUiBot.assertDatasets(c); 335 } 336 337 @Test 338 @AppModeFull(reason = "testFilter() is enough") testFilter_usingRegex()339 public void testFilter_usingRegex() throws Exception { 340 // Dataset presentations. 341 final String aa = "Two A's"; 342 final String ab = "A and B"; 343 final String b = "Only B"; 344 345 enableService(); 346 347 // Set expectations. 348 sReplier.addResponse(new CannedFillResponse.Builder() 349 .addDataset(new CannedDataset.Builder() 350 .setField(ID_USERNAME, "whatever", Pattern.compile("a|aa")) 351 .setPresentation(createPresentation(aa)) 352 .build()) 353 .addDataset(new CannedDataset.Builder() 354 .setField(ID_USERNAME, "whatsoever", createPresentation(ab), 355 Pattern.compile("a|ab")) 356 .build()) 357 .addDataset(new CannedDataset.Builder() 358 .setField(ID_USERNAME, (String) null, Pattern.compile("b")) 359 .setPresentation(createPresentation(b)) 360 .build()) 361 .build()); 362 363 // Trigger auto-fill. 364 requestFocusOnUsername(); 365 sReplier.getNextFillRequest(); 366 367 // With no filter text all datasets should be shown 368 mUiBot.assertDatasets(aa, ab, b); 369 370 // Only two datasets start with 'a' 371 changeUsername("a"); 372 mUiBot.assertDatasets(aa, ab); 373 374 // Only one dataset start with 'aa' 375 changeUsername("aa"); 376 mUiBot.assertDatasets(aa); 377 378 // Only two datasets start with 'a' 379 changeUsername("a"); 380 mUiBot.assertDatasets(aa, ab); 381 382 // With no filter text all datasets should be shown 383 changeUsername(""); 384 mUiBot.assertDatasets(aa, ab, b); 385 386 // No dataset start with 'aaa' 387 final MyAutofillCallback callback = mActivity.registerCallback(); 388 changeUsername("aaa"); 389 callback.assertUiHiddenEvent(mActivity.getUsername()); 390 mUiBot.assertNoDatasets(); 391 } 392 393 @Test 394 @AppModeFull(reason = "testFilter() is enough") testFilter_disabledUsingNullRegex()395 public void testFilter_disabledUsingNullRegex() throws Exception { 396 // Dataset presentations. 397 final String unfilterable = "Unfilterabled"; 398 final String aOrW = "A or W"; 399 final String w = "Wazzup"; 400 401 enableService(); 402 403 // Set expectations. 404 sReplier.addResponse(new CannedFillResponse.Builder() 405 // This dataset has a value but filter is disabled 406 .addDataset(new CannedDataset.Builder() 407 .setUnfilterableField(ID_USERNAME, "a am I") 408 .setPresentation(createPresentation(unfilterable)) 409 .build()) 410 // This dataset uses pattern to filter 411 .addDataset(new CannedDataset.Builder() 412 .setField(ID_USERNAME, "whatsoever", createPresentation(aOrW), 413 Pattern.compile("a|aw")) 414 .build()) 415 // This dataset uses value to filter 416 .addDataset(new CannedDataset.Builder() 417 .setField(ID_USERNAME, "wazzup") 418 .setPresentation(createPresentation(w)) 419 .build()) 420 .build()); 421 422 // Trigger auto-fill. 423 requestFocusOnUsername(); 424 sReplier.getNextFillRequest(); 425 426 // With no filter text all datasets should be shown 427 mUiBot.assertDatasets(unfilterable, aOrW, w); 428 429 // Only one dataset start with 'a' 430 changeUsername("a"); 431 mUiBot.assertDatasets(aOrW); 432 433 // No dataset starts with 'aa' 434 changeUsername("aa"); 435 mUiBot.assertNoDatasets(); 436 437 // Only one datasets start with 'a' 438 changeUsername("a"); 439 mUiBot.assertDatasets(aOrW); 440 441 // With no filter text all datasets should be shown 442 changeUsername(""); 443 mUiBot.assertDatasets(unfilterable, aOrW, w); 444 445 // Only one datasets start with 'w' 446 changeUsername("w"); 447 mUiBot.assertDatasets(w); 448 449 // No dataset start with 'aaa' 450 final MyAutofillCallback callback = mActivity.registerCallback(); 451 changeUsername("aaa"); 452 callback.assertUiHiddenEvent(mActivity.getUsername()); 453 mUiBot.assertNoDatasets(); 454 } 455 456 @Test 457 @AppModeFull(reason = "testFilter() is enough") testFilter_mixPlainAndRegex()458 public void testFilter_mixPlainAndRegex() throws Exception { 459 final String plain = "Plain"; 460 final String regexPlain = "RegexPlain"; 461 final String authRegex = "AuthRegex"; 462 final String kitchnSync = "KitchenSync"; 463 final Pattern everything = Pattern.compile(".*"); 464 465 enableService(); 466 467 // Set expectations. 468 final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1, 469 new CannedDataset.Builder() 470 .setField(ID_USERNAME, "dude") 471 .build()); 472 sReplier.addResponse(new CannedFillResponse.Builder() 473 .addDataset(new CannedDataset.Builder() 474 .setField(ID_USERNAME, "aword") 475 .setPresentation(createPresentation(plain)) 476 .build()) 477 .addDataset(new CannedDataset.Builder() 478 .setField(ID_USERNAME, "a ignore", everything) 479 .setPresentation(createPresentation(regexPlain)) 480 .build()) 481 .addDataset(new CannedDataset.Builder() 482 .setField(ID_USERNAME, "ab ignore", everything) 483 .setAuthentication(authentication) 484 .setPresentation(createPresentation(authRegex)) 485 .build()) 486 .addDataset(new CannedDataset.Builder() 487 .setField(ID_USERNAME, "ab ignore", createPresentation(kitchnSync), 488 everything) 489 .build()) 490 .build()); 491 492 // Trigger auto-fill. 493 requestFocusOnUsername(); 494 sReplier.getNextFillRequest(); 495 496 // With no filter text all datasets should be shown 497 mUiBot.assertDatasets(plain, regexPlain, authRegex, kitchnSync); 498 499 // All datasets start with 'a' 500 changeUsername("a"); 501 mUiBot.assertDatasets(plain, regexPlain, authRegex, kitchnSync); 502 503 // Only the regex datasets should start with 'ab' 504 changeUsername("ab"); 505 mUiBot.assertDatasets(regexPlain, authRegex, kitchnSync); 506 } 507 508 @Test 509 @AppModeFull(reason = "testFilter_usingKeyboard() is enough") testFilter_mixPlainAndRegex_usingKeyboard()510 public void testFilter_mixPlainAndRegex_usingKeyboard() throws Exception { 511 final String plain = "Plain"; 512 final String regexPlain = "RegexPlain"; 513 final String authRegex = "AuthRegex"; 514 final String kitchnSync = "KitchenSync"; 515 final Pattern everything = Pattern.compile(".*"); 516 517 enableService(); 518 519 // Set expectations. 520 final IntentSender authentication = AuthenticationActivity.createSender(mContext, 1, 521 new CannedDataset.Builder() 522 .setField(ID_USERNAME, "dude") 523 .build()); 524 sReplier.addResponse(new CannedFillResponse.Builder() 525 .addDataset(new CannedDataset.Builder() 526 .setField(ID_USERNAME, "aword") 527 .setPresentation(createPresentation(plain)) 528 .build()) 529 .addDataset(new CannedDataset.Builder() 530 .setField(ID_USERNAME, "a ignore", everything) 531 .setPresentation(createPresentation(regexPlain)) 532 .build()) 533 .addDataset(new CannedDataset.Builder() 534 .setField(ID_USERNAME, "ab ignore", everything) 535 .setAuthentication(authentication) 536 .setPresentation(createPresentation(authRegex)) 537 .build()) 538 .addDataset(new CannedDataset.Builder() 539 .setField(ID_USERNAME, "ab ignore", createPresentation(kitchnSync), 540 everything) 541 .build()) 542 .build()); 543 544 // Trigger auto-fill. 545 requestFocusOnUsername(); 546 sReplier.getNextFillRequest(); 547 548 // With no filter text all datasets should be shown 549 mUiBot.assertDatasets(plain, regexPlain, authRegex, kitchnSync); 550 551 // All datasets start with 'a' 552 sendKeyEvent("KEYCODE_A"); 553 mUiBot.assertDatasets(plain, regexPlain, authRegex, kitchnSync); 554 555 // Only the regex datasets should start with 'ab' 556 sendKeyEvent("KEYCODE_B"); 557 mUiBot.assertDatasets(regexPlain, authRegex, kitchnSync); 558 } 559 560 @Test 561 @AppModeFull(reason = "testFilter() is enough") testFilter_resetFilter_chooseFirst()562 public void testFilter_resetFilter_chooseFirst() throws Exception { 563 resetFilterTest(1); 564 } 565 566 @Test 567 @AppModeFull(reason = "testFilter() is enough") testFilter_resetFilter_chooseSecond()568 public void testFilter_resetFilter_chooseSecond() throws Exception { 569 resetFilterTest(2); 570 } 571 572 @Test 573 @AppModeFull(reason = "testFilter() is enough") testFilter_resetFilter_chooseThird()574 public void testFilter_resetFilter_chooseThird() throws Exception { 575 resetFilterTest(3); 576 } 577 resetFilterTest(int number)578 private void resetFilterTest(int number) throws Exception { 579 final String aa = "Two A's"; 580 final String ab = "A and B"; 581 final String b = "Only B"; 582 583 enableService(); 584 585 // Set expectations. 586 sReplier.addResponse(new CannedFillResponse.Builder() 587 .addDataset(new CannedDataset.Builder() 588 .setField(ID_USERNAME, "aa") 589 .setPresentation(createPresentation(aa)) 590 .build()) 591 .addDataset(new CannedDataset.Builder() 592 .setField(ID_USERNAME, "ab") 593 .setPresentation(createPresentation(ab)) 594 .build()) 595 .addDataset(new CannedDataset.Builder() 596 .setField(ID_USERNAME, "b") 597 .setPresentation(createPresentation(b)) 598 .build()) 599 .build()); 600 601 final String chosenOne; 602 switch (number) { 603 case 1: 604 chosenOne = aa; 605 mActivity.expectAutoFill("aa"); 606 break; 607 case 2: 608 chosenOne = ab; 609 mActivity.expectAutoFill("ab"); 610 break; 611 case 3: 612 chosenOne = b; 613 mActivity.expectAutoFill("b"); 614 break; 615 default: 616 throw new IllegalArgumentException("invalid dataset number: " + number); 617 } 618 619 final MyAutofillCallback callback = mActivity.registerCallback(); 620 final EditText username = mActivity.getUsername(); 621 622 // Trigger auto-fill. 623 requestFocusOnUsername(); 624 callback.assertUiShownEvent(username); 625 626 sReplier.getNextFillRequest(); 627 628 // With no filter text all datasets should be shown 629 mUiBot.assertDatasets(aa, ab, b); 630 631 // Only two datasets start with 'a' 632 changeUsername("a"); 633 mUiBot.assertDatasets(aa, ab); 634 635 // One dataset starts with 'aa' 636 changeUsername("aa"); 637 mUiBot.assertDatasets(aa); 638 639 // Filter all out 640 changeUsername("aaa"); 641 callback.assertUiHiddenEvent(username); 642 mUiBot.assertNoDatasets(); 643 644 // Now delete the char and assert aa is showing again 645 changeUsername("aa"); 646 callback.assertUiShownEvent(username); 647 mUiBot.assertDatasets(aa); 648 649 // Delete one more and assert two datasets showing 650 changeUsername("a"); 651 mUiBot.assertDatasets(aa, ab); 652 653 // Reset back to all choices 654 changeUsername(""); 655 mUiBot.assertDatasets(aa, ab, b); 656 657 // select the choice 658 mUiBot.selectDataset(chosenOne); 659 callback.assertUiHiddenEvent(username); 660 mUiBot.assertNoDatasets(); 661 662 // Check the results. 663 mActivity.assertAutoFilled(); 664 } 665 } 666