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.contentcaptureservice.cts; 18 19 import static android.contentcaptureservice.cts.Assertions.assertChildSessionContext; 20 import static android.contentcaptureservice.cts.Assertions.assertContextUpdated; 21 import static android.contentcaptureservice.cts.Assertions.assertDecorViewAppeared; 22 import static android.contentcaptureservice.cts.Assertions.assertMainSessionContext; 23 import static android.contentcaptureservice.cts.Assertions.assertRightActivity; 24 import static android.contentcaptureservice.cts.Assertions.assertRightRelationship; 25 import static android.contentcaptureservice.cts.Assertions.assertSessionId; 26 import static android.contentcaptureservice.cts.Assertions.assertSessionPaused; 27 import static android.contentcaptureservice.cts.Assertions.assertSessionResumed; 28 import static android.contentcaptureservice.cts.Assertions.assertViewAppeared; 29 import static android.contentcaptureservice.cts.Assertions.assertViewTreeFinished; 30 import static android.contentcaptureservice.cts.Assertions.assertViewTreeStarted; 31 import static android.contentcaptureservice.cts.Assertions.assertViewsOptionallyDisappeared; 32 import static android.contentcaptureservice.cts.Assertions.assertWindowBoundsChanged; 33 import static android.contentcaptureservice.cts.Helper.MY_PACKAGE; 34 import static android.contentcaptureservice.cts.Helper.newImportantView; 35 import static android.view.contentcapture.DataRemovalRequest.FLAG_IS_PREFIX; 36 37 import static com.android.compatibility.common.util.ActivitiesWatcher.ActivityLifecycle.DESTROYED; 38 import static com.android.compatibility.common.util.ActivitiesWatcher.ActivityLifecycle.RESUMED; 39 40 import static com.google.common.truth.Truth.assertThat; 41 import static com.google.common.truth.Truth.assertWithMessage; 42 43 import android.content.ComponentName; 44 import android.content.Context; 45 import android.content.ContextParams; 46 import android.content.LocusId; 47 import android.contentcaptureservice.cts.CtsContentCaptureService.Session; 48 import android.os.Bundle; 49 import android.platform.test.annotations.AppModeFull; 50 import android.text.Editable; 51 import android.text.Spannable; 52 import android.util.ArraySet; 53 import android.util.Log; 54 import android.view.View; 55 import android.view.WindowManager; 56 import android.view.autofill.AutofillId; 57 import android.view.contentcapture.ContentCaptureContext; 58 import android.view.contentcapture.ContentCaptureEvent; 59 import android.view.contentcapture.ContentCaptureSession; 60 import android.view.contentcapture.ContentCaptureSessionId; 61 import android.view.contentcapture.DataRemovalRequest; 62 import android.view.contentcapture.DataRemovalRequest.LocusIdRequest; 63 import android.view.inputmethod.BaseInputConnection; 64 import android.view.inputmethod.EditorInfo; 65 import android.view.inputmethod.InputConnection; 66 import android.widget.EditText; 67 import android.widget.LinearLayout; 68 import android.widget.TextView; 69 70 import androidx.annotation.NonNull; 71 import androidx.test.rule.ActivityTestRule; 72 73 import com.android.compatibility.common.util.ActivitiesWatcher.ActivityWatcher; 74 import com.android.compatibility.common.util.DoubleVisitor; 75 76 import org.junit.After; 77 import org.junit.Before; 78 import org.junit.Test; 79 80 import java.util.List; 81 import java.util.Set; 82 import java.util.concurrent.atomic.AtomicReference; 83 84 @AppModeFull(reason = "BlankWithTitleActivityTest is enough") 85 public class LoginActivityTest 86 extends AbstractContentCaptureIntegrationAutoActivityLaunchTest<LoginActivity> { 87 88 private static final String TAG = LoginActivityTest.class.getSimpleName(); 89 90 private static final int NO_FLAGS = 0; 91 92 private static final ActivityTestRule<LoginActivity> sActivityRule = new ActivityTestRule<>( 93 LoginActivity.class, false, false); 94 LoginActivityTest()95 public LoginActivityTest() { 96 super(LoginActivity.class); 97 } 98 99 @Override getActivityTestRule()100 protected ActivityTestRule<LoginActivity> getActivityTestRule() { 101 return sActivityRule; 102 } 103 104 @Before 105 @After resetActivityStaticState()106 public void resetActivityStaticState() { 107 LoginActivity.onRootView(null); 108 } 109 110 @Test testSimpleLifecycle_defaultSession()111 public void testSimpleLifecycle_defaultSession() throws Exception { 112 final CtsContentCaptureService service = enableService(); 113 final ActivityWatcher watcher = startWatcher(); 114 115 final LoginActivity activity = launchActivity(); 116 watcher.waitFor(RESUMED); 117 118 activity.finish(); 119 watcher.waitFor(DESTROYED); 120 121 final Session session = service.getOnlyFinishedSession(); 122 Log.v(TAG, "session id: " + session.id); 123 124 activity.assertDefaultEvents(session); 125 126 final ComponentName name = activity.getComponentName(); 127 service.assertThat() 128 .activityResumed(name) 129 .activityPaused(name); 130 } 131 132 @Test testContentCaptureSessionCache()133 public void testContentCaptureSessionCache() throws Exception { 134 final CtsContentCaptureService service = enableService(); 135 final ActivityWatcher watcher = startWatcher(); 136 137 final ContentCaptureContext clientContext = newContentCaptureContext(); 138 139 final AtomicReference<ContentCaptureSession> mainSessionRef = new AtomicReference<>(); 140 final AtomicReference<ContentCaptureSession> childSessionRef = new AtomicReference<>(); 141 142 LoginActivity.onRootView((activity, rootView) -> { 143 final ContentCaptureSession mainSession = rootView.getContentCaptureSession(); 144 mainSessionRef.set(mainSession); 145 final ContentCaptureSession childSession = mainSession 146 .createContentCaptureSession(clientContext); 147 childSessionRef.set(childSession); 148 149 rootView.setContentCaptureSession(childSession); 150 // Already called getContentCaptureSession() earlier, use cached session (main). 151 assertThat(rootView.getContentCaptureSession()).isEqualTo(childSession); 152 153 rootView.setContentCaptureSession(mainSession); 154 assertThat(rootView.getContentCaptureSession()).isEqualTo(mainSession); 155 156 rootView.setContentCaptureSession(childSession); 157 assertThat(rootView.getContentCaptureSession()).isEqualTo(childSession); 158 }); 159 160 final LoginActivity activity = launchActivity(); 161 watcher.waitFor(RESUMED); 162 163 activity.finish(); 164 watcher.waitFor(DESTROYED); 165 166 final ContentCaptureSessionId childSessionId = childSessionRef.get() 167 .getContentCaptureSessionId(); 168 169 assertSessionId(childSessionId, activity.getRootView()); 170 } 171 172 @Test testSimpleLifecycle_rootViewSession()173 public void testSimpleLifecycle_rootViewSession() throws Exception { 174 final CtsContentCaptureService service = enableService(); 175 final ActivityWatcher watcher = startWatcher(); 176 177 final ContentCaptureContext clientContext = newContentCaptureContext(); 178 179 final AtomicReference<ContentCaptureSession> mainSessionRef = new AtomicReference<>(); 180 final AtomicReference<ContentCaptureSession> childSessionRef = new AtomicReference<>(); 181 182 LoginActivity.onRootView((activity, rootView) -> { 183 final ContentCaptureSession mainSession = rootView.getContentCaptureSession(); 184 mainSessionRef.set(mainSession); 185 final ContentCaptureSession childSession = mainSession 186 .createContentCaptureSession(clientContext); 187 childSessionRef.set(childSession); 188 Log.i(TAG, "Setting root view (" + rootView + ") session to " + childSession); 189 rootView.setContentCaptureSession(childSession); 190 }); 191 192 final LoginActivity activity = launchActivity(); 193 watcher.waitFor(RESUMED); 194 195 activity.finish(); 196 watcher.waitFor(DESTROYED); 197 198 final ContentCaptureSessionId mainSessionId = mainSessionRef.get() 199 .getContentCaptureSessionId(); 200 final ContentCaptureSessionId childSessionId = childSessionRef.get() 201 .getContentCaptureSessionId(); 202 Log.v(TAG, "session ids: main=" + mainSessionId + ", child=" + childSessionId); 203 204 // Sanity checks 205 assertSessionId(childSessionId, activity.getRootView()); 206 assertSessionId(childSessionId, activity.mUsernameLabel); 207 assertSessionId(childSessionId, activity.mUsername); 208 assertSessionId(childSessionId, activity.mPassword); 209 assertSessionId(childSessionId, activity.mPasswordLabel); 210 211 // Get the sessions 212 final Session mainSession = service.getFinishedSession(mainSessionId); 213 final Session childSession = service.getFinishedSession(childSessionId); 214 215 assertRightActivity(mainSession, mainSessionId, activity); 216 assertRightRelationship(mainSession, childSession); 217 218 // Sanity check 219 final List<ContentCaptureSessionId> allSessionIds = service.getAllSessionIds(); 220 assertThat(allSessionIds).containsExactly(mainSessionId, childSessionId); 221 222 /* 223 * Asserts main session 224 */ 225 226 // Checks context 227 assertMainSessionContext(mainSession, activity); 228 229 // Check events 230 final List<ContentCaptureEvent> unfilteredEvents = mainSession.getUnfilteredEvents(); 231 assertWindowBoundsChanged(unfilteredEvents); 232 233 final List<ContentCaptureEvent> mainEvents = mainSession.getEvents(); 234 Log.v(TAG, "events(" + mainEvents.size() + ") for main session: " + mainEvents); 235 236 final View grandpa1 = activity.getGrandParent(); 237 final View grandpa2 = activity.getGrandGrandParent(); 238 final View decorView = activity.getDecorView(); 239 final AutofillId rootId = activity.getRootView().getAutofillId(); 240 241 final int minEvents = 7; // TODO(b/122315042): disappeared not always sent 242 assertThat(mainEvents.size()).isAtLeast(minEvents); 243 assertSessionResumed(mainEvents, 0); 244 assertViewTreeStarted(mainEvents, 1); 245 assertDecorViewAppeared(mainEvents, 2, decorView); 246 assertViewAppeared(mainEvents, 3, grandpa2, decorView.getAutofillId()); 247 assertViewAppeared(mainEvents, 4, grandpa1, grandpa2.getAutofillId()); 248 assertViewTreeFinished(mainEvents, 5); 249 // TODO(b/122315042): these assertions are currently a mess, so let's disable for now and 250 // properly fix them later... 251 if (false) { 252 int pausedIndex = 6; 253 final boolean disappeared = assertViewsOptionallyDisappeared(mainEvents, pausedIndex, 254 decorView.getAutofillId(), 255 grandpa2.getAutofillId(), grandpa1.getAutofillId()); 256 if (disappeared) { 257 pausedIndex += 3; 258 } 259 assertSessionPaused(mainEvents, pausedIndex); 260 } 261 262 /* 263 * Asserts child session 264 */ 265 266 // Checks context 267 assertChildSessionContext(childSession, "file://dev/null"); 268 269 assertContentCaptureContext(childSession.context); 270 271 // Check events 272 final List<ContentCaptureEvent> childEvents = childSession.getEvents(); 273 Log.v(TAG, "events for child session: " + childEvents); 274 final int minChildEvents = 5; 275 assertThat(childEvents.size()).isAtLeast(minChildEvents); 276 assertViewAppeared(childEvents, 0, childSessionId, activity.getRootView(), 277 grandpa1.getAutofillId()); 278 assertViewAppeared(childEvents, 1, childSessionId, activity.mUsernameLabel, rootId); 279 assertViewAppeared(childEvents, 2, childSessionId, activity.mUsername, rootId); 280 assertViewAppeared(childEvents, 3, childSessionId, activity.mPasswordLabel, rootId); 281 assertViewAppeared(childEvents, 4, childSessionId, activity.mPassword, rootId); 282 283 assertViewsOptionallyDisappeared(childEvents, minChildEvents, 284 rootId, 285 activity.mUsernameLabel.getAutofillId(), activity.mUsername.getAutofillId(), 286 activity.mPasswordLabel.getAutofillId(), activity.mPassword.getAutofillId()); 287 } 288 289 @Test testSimpleLifecycle_changeContextAfterCreate()290 public void testSimpleLifecycle_changeContextAfterCreate() throws Exception { 291 final CtsContentCaptureService service = enableService(); 292 final ActivityWatcher watcher = startWatcher(); 293 294 final LoginActivity activity = launchActivity(); 295 watcher.waitFor(RESUMED); 296 297 final ContentCaptureContext newContext1 = newContentCaptureContext(); 298 final ContentCaptureContext newContext2 = null; 299 300 final View rootView = activity.getRootView(); 301 final ContentCaptureSession mainSession = rootView.getContentCaptureSession(); 302 assertThat(mainSession).isNotNull(); 303 Log.i(TAG, "Updating root view (" + rootView + ") context to " + newContext1); 304 mainSession.setContentCaptureContext(newContext1); 305 assertContentCaptureContext(mainSession.getContentCaptureContext()); 306 307 Log.i(TAG, "Updating root view (" + rootView + ") context to " + newContext2); 308 mainSession.setContentCaptureContext(newContext2); 309 310 activity.finish(); 311 watcher.waitFor(DESTROYED); 312 313 final Session session = service.getOnlyFinishedSession(); 314 Log.v(TAG, "session id: " + session.id); 315 316 final EventsAssertor assertor = activity.assertInitialViewsAppeared(session); 317 318 assertor.isAtLeast(LoginActivity.MIN_EVENTS + 2) 319 .assertContextUpdated(); 320 321 final ContentCaptureEvent event1 = assertor.getLastEvent(); 322 final ContentCaptureContext actualContext = event1.getContentCaptureContext(); 323 assertContentCaptureContext(actualContext); 324 325 assertor.assertContextUpdated(); 326 327 final ContentCaptureEvent event2 = assertor.getLastEvent(); 328 assertThat(event2.getContentCaptureContext()).isNull(); 329 } 330 331 @Test testSimpleLifecycle_changeContextOnCreate()332 public void testSimpleLifecycle_changeContextOnCreate() throws Exception { 333 final CtsContentCaptureService service = enableService(); 334 final ActivityWatcher watcher = startWatcher(); 335 336 final ContentCaptureContext newContext = newContentCaptureContext(); 337 338 LoginActivity.onRootView((activity, rootView) -> { 339 final ContentCaptureSession mainSession = rootView.getContentCaptureSession(); 340 Log.i(TAG, "Setting root view (" + rootView + ") context to " + newContext); 341 mainSession.setContentCaptureContext(newContext); 342 assertContentCaptureContext(mainSession.getContentCaptureContext()); 343 }); 344 345 final LoginActivity activity = launchActivity(); 346 watcher.waitFor(RESUMED); 347 348 activity.finish(); 349 watcher.waitFor(DESTROYED); 350 351 final Session session = service.getOnlyFinishedSession(); 352 Log.v(TAG, "session id: " + session.id); 353 final ContentCaptureSessionId sessionId = session.id; 354 assertRightActivity(session, sessionId, activity); 355 356 // Sanity check 357 358 final List<ContentCaptureEvent> events = session.getEvents(); 359 Log.v(TAG, "events(" + events.size() + "): " + events); 360 // TODO(b/123540067): ideally it should be X so it reflects just the views defined 361 // in the layout - right now it's generating events for 2 intermediate parents 362 // (android:action_mode_bar_stub and android:content), we should try to create an 363 // activity without them 364 365 final AutofillId rootId = activity.getRootView().getAutofillId(); 366 367 assertThat(events.size()).isAtLeast(11); 368 369 // TODO(b/123540067): get rid of those intermediated parents 370 final View grandpa1 = activity.getGrandParent(); 371 final View grandpa2 = activity.getGrandGrandParent(); 372 final View decorView = activity.getDecorView(); 373 final View rootView = activity.getRootView(); 374 375 final ContentCaptureEvent ctxUpdatedEvent = assertContextUpdated(events, 0); 376 final ContentCaptureContext actualContext = ctxUpdatedEvent.getContentCaptureContext(); 377 assertContentCaptureContext(actualContext); 378 379 assertSessionResumed(events, 1); 380 assertViewTreeStarted(events, 2); 381 assertDecorViewAppeared(events, 3, decorView); 382 assertViewAppeared(events, 4, grandpa2, decorView.getAutofillId()); 383 assertViewAppeared(events, 5, grandpa1, grandpa2.getAutofillId()); 384 assertViewAppeared(events, 6, sessionId, rootView, grandpa1.getAutofillId()); 385 assertViewAppeared(events, 7, sessionId, activity.mUsernameLabel, rootId); 386 assertViewAppeared(events, 8, sessionId, activity.mUsername, rootId); 387 assertViewAppeared(events, 9, sessionId, activity.mPasswordLabel, rootId); 388 assertViewAppeared(events, 10, sessionId, activity.mPassword, rootId); 389 } 390 391 @Test testTextChanged()392 public void testTextChanged() throws Exception { 393 final CtsContentCaptureService service = enableService(); 394 final ActivityWatcher watcher = startWatcher(); 395 396 LoginActivity.onRootView((activity, rootView) -> ((LoginActivity) activity).mUsername 397 .setText("user")); 398 399 final LoginActivity activity = launchActivity(); 400 watcher.waitFor(RESUMED); 401 402 activity.syncRunOnUiThread(() -> { 403 activity.mUsername.setText("USER"); 404 activity.mPassword.setText("PASS"); 405 }); 406 407 activity.finish(); 408 watcher.waitFor(DESTROYED); 409 410 final Session session = service.getOnlyFinishedSession(); 411 412 final EventsAssertor assertor = activity.assertInitialViewsAppeared(session); 413 414 assertor.isAtLeast(LoginActivity.MIN_EVENTS + 2) 415 .assertViewTextChanged(activity.mUsername.getAutofillId(), "USER") 416 .assertViewTextChanged(activity.mPassword.getAutofillId(), "PASS"); 417 418 activity.assertInitialViewsDisappeared(assertor); 419 } 420 421 @Test testTextChangeBuffer()422 public void testTextChangeBuffer() throws Exception { 423 final CtsContentCaptureService service = enableService(); 424 final ActivityWatcher watcher = startWatcher(); 425 426 LoginActivity.onRootView((activity, rootView) -> ((LoginActivity) activity).mUsername 427 .setText("")); 428 429 final LoginActivity activity = launchActivity(); 430 watcher.waitFor(RESUMED); 431 432 activity.syncRunOnUiThread(() -> { 433 activity.mUsername.setText("a"); 434 activity.mUsername.setText("ab"); 435 activity.mUsername.setText(""); 436 activity.mUsername.setText("abc"); 437 438 activity.mPassword.setText("d"); 439 activity.mPassword.setText(""); 440 activity.mPassword.setText(""); 441 activity.mPassword.setText("de"); 442 activity.mPassword.setText("def"); 443 activity.mPassword.setText(""); 444 445 activity.mUsername.setText("abc"); 446 }); 447 448 activity.finish(); 449 watcher.waitFor(DESTROYED); 450 451 final Session session = service.getOnlyFinishedSession(); 452 453 final EventsAssertor assertor = activity.assertInitialViewsAppeared(session); 454 455 final AutofillId usernameId = activity.mUsername.getAutofillId(); 456 final AutofillId passwordId = activity.mPassword.getAutofillId(); 457 458 assertor.isAtLeast(LoginActivity.MIN_EVENTS + 8) 459 .assertViewTextChanged(activity.mUsername.getAutofillId(), "a") 460 .assertViewTextChanged(usernameId, "ab") 461 .assertViewTextChanged(usernameId, "") 462 .assertViewTextChanged(usernameId, "abc") 463 .assertViewTextChanged(passwordId, "d") 464 .assertViewTextChanged(passwordId, "") 465 .assertViewTextChanged(passwordId, "") 466 .assertViewTextChanged(passwordId, "de") 467 .assertViewTextChanged(passwordId, "def") 468 .assertViewTextChanged(passwordId, "") 469 .assertViewTextChanged(usernameId, "abc"); 470 471 activity.assertInitialViewsDisappeared(assertor); 472 } 473 474 @Test testComposingSpan_mergedEvent()475 public void testComposingSpan_mergedEvent() throws Exception { 476 final CtsContentCaptureService service = enableService(); 477 final ActivityWatcher watcher = startWatcher(); 478 479 LoginActivity.onRootView((activity, rootView) -> ((LoginActivity) activity).mUsername 480 .setText("")); 481 482 final LoginActivity activity = launchActivity(); 483 watcher.waitFor(RESUMED); 484 485 activity.syncRunOnUiThread(() -> { 486 // add text with composing span. 487 appendText(activity.mUsername, "A"); 488 appendText(activity.mUsername, "n"); 489 appendText(activity.mUsername, "d"); 490 appendText(activity.mUsername, "r"); 491 appendText(activity.mUsername, "o"); 492 appendText(activity.mUsername, "i"); 493 appendText(activity.mUsername, "d"); 494 }); 495 496 activity.finish(); 497 watcher.waitFor(DESTROYED); 498 499 final Session session = service.getOnlyFinishedSession(); 500 501 final EventsAssertor assertor = activity.assertInitialViewsAppeared(session); 502 503 assertor.isAtLeast(LoginActivity.MIN_EVENTS + 5) 504 .assertViewTextChanged(activity.mUsername.getAutofillId(), "Android"); 505 506 activity.assertInitialViewsDisappeared(assertor); 507 } 508 509 @Test testComposingSpan_notMergedWithoutComposing()510 public void testComposingSpan_notMergedWithoutComposing() throws Exception { 511 final CtsContentCaptureService service = enableService(); 512 final ActivityWatcher watcher = startWatcher(); 513 514 LoginActivity.onRootView((activity, rootView) -> ((LoginActivity) activity).mUsername 515 .setText("")); 516 517 final LoginActivity activity = launchActivity(); 518 watcher.waitFor(RESUMED); 519 520 activity.syncRunOnUiThread(() -> { 521 // add text with composing span. 522 appendText(activity.mUsername, "G"); 523 appendText(activity.mUsername, "o"); 524 appendText(activity.mUsername, "o"); 525 appendText(activity.mUsername, "d"); 526 527 // append text without composing span 528 appendText(activity.mUsername, " ", false); 529 530 // append text with composing span, again. 531 appendText(activity.mUsername, "m"); 532 appendText(activity.mUsername, "orning"); 533 }); 534 535 activity.finish(); 536 watcher.waitFor(DESTROYED); 537 538 final Session session = service.getOnlyFinishedSession(); 539 540 final EventsAssertor assertor = activity.assertInitialViewsAppeared(session); 541 542 assertor.isAtLeast(LoginActivity.MIN_EVENTS + 4) 543 .assertViewTextChanged(activity.mUsername.getAutofillId(), "Good"); 544 assertComposingSpan(assertor.getLastEvent().getText(), 0, 4); 545 546 assertor.assertViewTextChanged(activity.mUsername.getAutofillId(), "Good "); 547 assertNoComposingSpan(assertor.getLastEvent().getText()); 548 549 assertor.assertViewTextChanged(activity.mUsername.getAutofillId(), "Good morning"); 550 // TODO: Change how the appending works to more realistically test the case where only 551 // "morning" is in the composing state. 552 assertComposingSpan(assertor.getLastEvent().getText(), 0, 12); 553 554 activity.assertInitialViewsDisappeared(assertor); 555 } 556 557 @Test testComposingSpan_differentEditText()558 public void testComposingSpan_differentEditText() throws Exception { 559 final CtsContentCaptureService service = enableService(); 560 final ActivityWatcher watcher = startWatcher(); 561 562 LoginActivity.onRootView((activity, rootView) -> ((LoginActivity) activity).mUsername 563 .setText("")); 564 565 final LoginActivity activity = launchActivity(); 566 watcher.waitFor(RESUMED); 567 568 activity.syncRunOnUiThread(() -> { 569 // add text with composing span. 570 appendText(activity.mUsername, "Good"); 571 // add text with composing span on the different EditText. 572 appendText(activity.mPassword, "How"); 573 // switch again. 574 appendText(activity.mUsername, " morning"); 575 appendText(activity.mPassword, " are you"); 576 }); 577 578 activity.finish(); 579 watcher.waitFor(DESTROYED); 580 581 final Session session = service.getOnlyFinishedSession(); 582 583 final EventsAssertor assertor = activity.assertInitialViewsAppeared(session); 584 585 assertor.isAtLeast(LoginActivity.MIN_EVENTS + 3) 586 .assertViewTextChanged(activity.mUsername.getAutofillId(), "Good morning") 587 .assertViewTextChanged(activity.mPassword.getAutofillId(), "How are you"); 588 589 activity.assertInitialViewsDisappeared(assertor); 590 } 591 592 @Test testComposingSpan_eventsForSpanChanges()593 public void testComposingSpan_eventsForSpanChanges() throws Exception { 594 final CtsContentCaptureService service = enableService(); 595 final ActivityWatcher watcher = startWatcher(); 596 597 LoginActivity.onRootView((activity, rootView) -> ((LoginActivity) activity).mUsername 598 .setText("")); 599 600 final LoginActivity activity = launchActivity(); 601 watcher.waitFor(RESUMED); 602 603 activity.syncRunOnUiThread(() -> { 604 activity.mUsername.setText("Android"); 605 final InputConnection inputConnection = 606 activity.mUsername.onCreateInputConnection(new EditorInfo()); 607 608 // These 2 should be merged. 609 inputConnection.setComposingRegion(1, 2); 610 inputConnection.setComposingRegion(1, 3); 611 612 inputConnection.finishComposingText(); 613 activity.mUsername.setText("end"); 614 // TODO: Test setComposingText. 615 }); 616 617 activity.finish(); 618 watcher.waitFor(DESTROYED); 619 620 final Session session = service.getOnlyFinishedSession(); 621 622 final EventsAssertor assertor = activity.assertInitialViewsAppeared(session); 623 624 assertor.isAtLeast(LoginActivity.MIN_EVENTS + 5) 625 // TODO: The first two events should probably be merged. 626 .assertViewTextChanged(activity.mUsername.getAutofillId(), "Android"); 627 assertNoComposingSpan(assertor.getLastEvent().getText()); 628 629 assertor.assertViewTextChanged(activity.mUsername.getAutofillId(), "Android"); 630 assertComposingSpan(assertor.getLastEvent().getText(), 1, 3); 631 632 assertor.assertViewTextChanged(activity.mUsername.getAutofillId(), "Android"); 633 assertNoComposingSpan(assertor.getLastEvent().getText()); 634 635 assertor.assertViewTextChanged(activity.mUsername.getAutofillId(), "end"); 636 assertNoComposingSpan(assertor.getLastEvent().getText()); 637 638 activity.assertInitialViewsDisappeared(assertor); 639 } 640 appendText(EditText editText, String text)641 private void appendText(EditText editText, String text) { 642 appendText(editText, text, true); 643 } 644 appendText(EditText editText, String text, boolean hasComposingSpan)645 private void appendText(EditText editText, String text, boolean hasComposingSpan) { 646 Editable editable = editText.getText(); 647 String s = editable.toString() + text; 648 Editable newEditable = Editable.Factory.getInstance().newEditable(s); 649 if (hasComposingSpan) { 650 BaseInputConnection.setComposingSpans(newEditable); 651 } else { 652 BaseInputConnection.removeComposingSpans(editable); 653 } 654 editable.replace(0, editable.length() , newEditable); 655 } 656 657 @Test testDisabledByFlagSecure()658 public void testDisabledByFlagSecure() throws Exception { 659 final CtsContentCaptureService service = enableService(); 660 final ActivityWatcher watcher = startWatcher(); 661 662 LoginActivity.onRootView((activity, rootView) -> activity.getWindow() 663 .addFlags(WindowManager.LayoutParams.FLAG_SECURE)); 664 665 final LoginActivity activity = launchActivity(); 666 watcher.waitFor(RESUMED); 667 668 activity.finish(); 669 watcher.waitFor(DESTROYED); 670 671 final Session session = service.getOnlyFinishedSession(); 672 assertThat((session.context.getFlags() 673 & ContentCaptureContext.FLAG_DISABLED_BY_FLAG_SECURE) != 0).isTrue(); 674 final ContentCaptureSessionId sessionId = session.id; 675 Log.v(TAG, "session id: " + sessionId); 676 677 assertRightActivity(session, sessionId, activity); 678 679 final List<ContentCaptureEvent> events = session.getEvents(); 680 assertThat(events).isEmpty(); 681 } 682 683 @Test testDisabledByApp()684 public void testDisabledByApp() throws Exception { 685 final CtsContentCaptureService service = enableService(); 686 final ActivityWatcher watcher = startWatcher(); 687 688 LoginActivity.onRootView((activity, rootView) -> activity.getContentCaptureManager() 689 .setContentCaptureEnabled(false)); 690 691 final LoginActivity activity = launchActivity(); 692 watcher.waitFor(RESUMED); 693 694 assertThat(activity.getContentCaptureManager().isContentCaptureEnabled()).isFalse(); 695 696 activity.syncRunOnUiThread(() -> activity.mUsername.setText("D'OH")); 697 698 activity.finish(); 699 watcher.waitFor(DESTROYED); 700 701 final Session session = service.getOnlyFinishedSession(); 702 assertThat((session.context.getFlags() 703 & ContentCaptureContext.FLAG_DISABLED_BY_APP) != 0).isTrue(); 704 final ContentCaptureSessionId sessionId = session.id; 705 Log.v(TAG, "session id: " + sessionId); 706 707 assertRightActivity(session, sessionId, activity); 708 709 final List<ContentCaptureEvent> events = session.getEvents(); 710 assertThat(events).isEmpty(); 711 } 712 713 @Test testDisabledFlagSecureAndByApp()714 public void testDisabledFlagSecureAndByApp() throws Exception { 715 final CtsContentCaptureService service = enableService(); 716 final ActivityWatcher watcher = startWatcher(); 717 718 LoginActivity.onRootView((activity, rootView) -> { 719 activity.getContentCaptureManager().setContentCaptureEnabled(false); 720 activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE); 721 }); 722 723 final LoginActivity activity = launchActivity(); 724 watcher.waitFor(RESUMED); 725 726 assertThat(activity.getContentCaptureManager().isContentCaptureEnabled()).isFalse(); 727 activity.syncRunOnUiThread(() -> activity.mUsername.setText("D'OH")); 728 729 activity.finish(); 730 watcher.waitFor(DESTROYED); 731 732 final Session session = service.getOnlyFinishedSession(); 733 assertThat((session.context.getFlags() 734 & ContentCaptureContext.FLAG_DISABLED_BY_APP) != 0).isTrue(); 735 assertThat((session.context.getFlags() 736 & ContentCaptureContext.FLAG_DISABLED_BY_FLAG_SECURE) != 0).isTrue(); 737 final ContentCaptureSessionId sessionId = session.id; 738 Log.v(TAG, "session id: " + sessionId); 739 740 assertRightActivity(session, sessionId, activity); 741 742 final List<ContentCaptureEvent> events = session.getEvents(); 743 assertThat(events).isEmpty(); 744 } 745 746 @Test testUserDataRemovalRequest_forEverything()747 public void testUserDataRemovalRequest_forEverything() throws Exception { 748 final CtsContentCaptureService service = enableService(); 749 final ActivityWatcher watcher = startWatcher(); 750 751 LoginActivity.onRootView((activity, rootView) -> activity.getContentCaptureManager() 752 .removeData(new DataRemovalRequest.Builder().forEverything() 753 .build())); 754 755 final LoginActivity activity = launchActivity(); 756 watcher.waitFor(RESUMED); 757 758 activity.finish(); 759 watcher.waitFor(DESTROYED); 760 761 DataRemovalRequest request = service.getRemovalRequest(); 762 assertThat(request).isNotNull(); 763 assertThat(request.isForEverything()).isTrue(); 764 assertThat(request.getLocusIdRequests()).isNull(); 765 assertThat(request.getPackageName()).isEqualTo(MY_PACKAGE); 766 } 767 768 @Test testUserDataRemovalRequest_oneId()769 public void testUserDataRemovalRequest_oneId() throws Exception { 770 final CtsContentCaptureService service = enableService(); 771 final ActivityWatcher watcher = startWatcher(); 772 773 final LocusId locusId = new LocusId("com.example"); 774 775 LoginActivity.onRootView((activity, rootView) -> activity.getContentCaptureManager() 776 .removeData(new DataRemovalRequest.Builder() 777 .addLocusId(locusId, NO_FLAGS) 778 .build())); 779 780 final LoginActivity activity = launchActivity(); 781 watcher.waitFor(RESUMED); 782 783 activity.finish(); 784 watcher.waitFor(DESTROYED); 785 786 DataRemovalRequest request = service.getRemovalRequest(); 787 assertThat(request).isNotNull(); 788 assertThat(request.isForEverything()).isFalse(); 789 assertThat(request.getPackageName()).isEqualTo(MY_PACKAGE); 790 791 final List<LocusIdRequest> requests = request.getLocusIdRequests(); 792 assertThat(requests.size()).isEqualTo(1); 793 794 final LocusIdRequest actualRequest = requests.get(0); 795 assertThat(actualRequest.getLocusId()).isEqualTo(locusId); 796 assertThat(actualRequest.getFlags()).isEqualTo(NO_FLAGS); 797 } 798 799 @Test testUserDataRemovalRequest_manyIds()800 public void testUserDataRemovalRequest_manyIds() throws Exception { 801 final CtsContentCaptureService service = enableService(); 802 final ActivityWatcher watcher = startWatcher(); 803 804 final LocusId locusId1 = new LocusId("com.example"); 805 final LocusId locusId2 = new LocusId("com.example2"); 806 807 LoginActivity.onRootView((activity, rootView) -> activity.getContentCaptureManager() 808 .removeData(new DataRemovalRequest.Builder() 809 .addLocusId(locusId1, NO_FLAGS) 810 .addLocusId(locusId2, FLAG_IS_PREFIX) 811 .build())); 812 813 final LoginActivity activity = launchActivity(); 814 watcher.waitFor(RESUMED); 815 816 activity.finish(); 817 watcher.waitFor(DESTROYED); 818 819 final DataRemovalRequest request = service.getRemovalRequest(); 820 assertThat(request).isNotNull(); 821 assertThat(request.isForEverything()).isFalse(); 822 assertThat(request.getPackageName()).isEqualTo(MY_PACKAGE); 823 824 final List<LocusIdRequest> requests = request.getLocusIdRequests(); 825 assertThat(requests.size()).isEqualTo(2); 826 827 final LocusIdRequest actualRequest1 = requests.get(0); 828 assertThat(actualRequest1.getLocusId()).isEqualTo(locusId1); 829 assertThat(actualRequest1.getFlags()).isEqualTo(NO_FLAGS); 830 831 final LocusIdRequest actualRequest2 = requests.get(1); 832 assertThat(actualRequest2.getLocusId()).isEqualTo(locusId2); 833 assertThat(actualRequest2.getFlags()).isEqualTo(FLAG_IS_PREFIX); 834 } 835 836 @Test testAddChildren_rightAway()837 public void testAddChildren_rightAway() throws Exception { 838 final CtsContentCaptureService service = enableService(); 839 final ActivityWatcher watcher = startWatcher(); 840 final View[] children = new View[2]; 841 842 final DoubleVisitor<AbstractRootViewActivity, LinearLayout> visitor = (activity, 843 rootView) -> { 844 final TextView child1 = newImportantView(activity, "c1"); 845 children[0] = child1; 846 Log.v(TAG, "Adding child1(" + child1.getAutofillId() + "): " + child1); 847 rootView.addView(child1); 848 final TextView child2 = newImportantView(activity, "c1"); 849 children[1] = child2; 850 Log.v(TAG, "Adding child2(" + child2.getAutofillId() + "): " + child2); 851 rootView.addView(child2); 852 }; 853 LoginActivity.onRootView(visitor); 854 855 final LoginActivity activity = launchActivity(); 856 watcher.waitFor(RESUMED); 857 858 activity.finish(); 859 watcher.waitFor(DESTROYED); 860 861 final Session session = service.getOnlyFinishedSession(); 862 Log.v(TAG, "session id: " + session.id); 863 864 final ContentCaptureSessionId sessionId = session.id; 865 assertRightActivity(session, sessionId, activity); 866 867 final List<ContentCaptureEvent> events = activity.assertJustInitialViewsAppeared(session, 868 /* additionalEvents= */ 2); 869 final AutofillId rootId = activity.getRootView().getAutofillId(); 870 int i = LoginActivity.MIN_EVENTS - 1; 871 assertViewAppeared(events, i, sessionId, children[0], rootId); 872 assertViewAppeared(events, i + 1, sessionId, children[1], rootId); 873 assertViewTreeFinished(events, i + 2); 874 875 activity.assertInitialViewsDisappeared(events, children.length); 876 } 877 878 @Test testViewAppeared_withNewContext()879 public void testViewAppeared_withNewContext() throws Exception { 880 final CtsContentCaptureService service = enableService(); 881 final ActivityWatcher watcher = startWatcher(); 882 883 final LoginActivity activity = launchActivity(); 884 watcher.waitFor(RESUMED); 885 886 // Add View 887 final LinearLayout rootView = activity.getRootView(); 888 final Context newContext = activity.createContext(new ContextParams.Builder().build()); 889 final TextView child = newImportantView(newContext, "Important I am"); 890 activity.runOnUiThread(() -> rootView.addView(child)); 891 892 activity.finish(); 893 watcher.waitFor(DESTROYED); 894 895 final Session session = service.getOnlyFinishedSession(); 896 final ContentCaptureSessionId sessionId = session.id; 897 Log.v(TAG, "session id: " + sessionId); 898 final AutofillId rootId = activity.getRootView().getAutofillId(); 899 900 final EventsAssertor assertor = activity.assertInitialViewsAppeared(session); 901 902 assertor.isAtLeast(LoginActivity.MIN_EVENTS + 3) 903 .assertViewTreeStarted() 904 .assertViewAppeared(sessionId, child, rootId) 905 .assertViewTreeFinished(); 906 } 907 908 @Test testAddChildren_afterAnimation()909 public void testAddChildren_afterAnimation() throws Exception { 910 final CtsContentCaptureService service = enableService(); 911 final ActivityWatcher watcher = startWatcher(); 912 final View[] children = new View[2]; 913 914 final DoubleVisitor<AbstractRootViewActivity, LinearLayout> visitor = (activity, 915 rootView) -> { 916 final TextView child1 = newImportantView(activity, "c1"); 917 children[0] = child1; 918 Log.v(TAG, "Adding child1(" + child1.getAutofillId() + "): " + child1); 919 rootView.addView(child1); 920 final TextView child2 = newImportantView(activity, "c1"); 921 children[1] = child2; 922 Log.v(TAG, "Adding child2(" + child2.getAutofillId() + "): " + child2); 923 rootView.addView(child2); 924 }; 925 LoginActivity.onAnimationComplete(visitor); 926 927 final LoginActivity activity = launchActivity(); 928 watcher.waitFor(RESUMED); 929 930 activity.finish(); 931 watcher.waitFor(DESTROYED); 932 933 final Session session = service.getOnlyFinishedSession(); 934 Log.v(TAG, "session id: " + session.id); 935 final ContentCaptureSessionId sessionId = session.id; 936 final View decorView = activity.getDecorView(); 937 final View grandpa1 = activity.getGrandParent(); 938 final View grandpa2 = activity.getGrandGrandParent(); 939 final AutofillId rootId = activity.getRootView().getAutofillId(); 940 941 final EventsAssertor assertor = activity.assertInitialViewsAppeared(session); 942 943 assertor.isAtLeast(LoginActivity.MIN_EVENTS + 5) 944 .assertViewTreeStarted() 945 .assertViewAppeared(sessionId, children[0], rootId) 946 .assertViewAppeared(sessionId, children[1], rootId) 947 .assertViewTreeFinished() 948 .assertViewDisappeared( 949 decorView.getAutofillId(), 950 grandpa1.getAutofillId(), grandpa2.getAutofillId(), 951 rootId, 952 activity.mUsernameLabel.getAutofillId(), activity.mUsername.getAutofillId(), 953 activity.mPasswordLabel.getAutofillId(), activity.mPassword.getAutofillId(), 954 children[0].getAutofillId(), children[1].getAutofillId()); 955 } 956 957 @Test testWhitelist_packageNotWhitelisted()958 public void testWhitelist_packageNotWhitelisted() throws Exception { 959 final CtsContentCaptureService service = enableService(); 960 final ActivityWatcher watcher = startWatcher(); 961 962 service.setContentCaptureWhitelist((Set) null, (Set) null); 963 964 final LoginActivity activity = launchActivity(); 965 watcher.waitFor(RESUMED); 966 967 activity.finish(); 968 watcher.waitFor(DESTROYED); 969 970 assertThat(service.getAllSessionIds()).isEmpty(); 971 } 972 973 @Test testWhitelist_activityNotWhitelisted()974 public void testWhitelist_activityNotWhitelisted() throws Exception { 975 final CtsContentCaptureService service = enableService(); 976 final ArraySet<ComponentName> components = new ArraySet<>(); 977 components.add(new ComponentName(MY_PACKAGE, "some.activity")); 978 service.setContentCaptureWhitelist(null, components); 979 final ActivityWatcher watcher = startWatcher(); 980 981 final LoginActivity activity = launchActivity(); 982 watcher.waitFor(RESUMED); 983 984 activity.finish(); 985 watcher.waitFor(DESTROYED); 986 987 assertThat(service.getAllSessionIds()).isEmpty(); 988 } 989 990 /** 991 * Creates a context that can be assert by 992 * {@link #assertContentCaptureContext(ContentCaptureContext)}. 993 */ newContentCaptureContext()994 private ContentCaptureContext newContentCaptureContext() { 995 final String id = "file://dev/null"; 996 final Bundle bundle = new Bundle(); 997 bundle.putString("DUDE", "SWEET"); 998 return new ContentCaptureContext.Builder(new LocusId(id)).setExtras(bundle).build(); 999 } 1000 1001 /** 1002 * Asserts a context that can has been created by {@link #newContentCaptureContext()}. 1003 */ assertContentCaptureContext(@onNull ContentCaptureContext context)1004 private void assertContentCaptureContext(@NonNull ContentCaptureContext context) { 1005 assertWithMessage("null context").that(context).isNotNull(); 1006 assertWithMessage("wrong ID on context %s", context).that(context.getLocusId().getId()) 1007 .isEqualTo("file://dev/null"); 1008 final Bundle extras = context.getExtras(); 1009 assertWithMessage("no extras on context %s", context).that(extras).isNotNull(); 1010 assertWithMessage("wrong number of extras on context %s", context).that(extras.size()) 1011 .isEqualTo(1); 1012 assertWithMessage("wrong extras on context %s", context).that(extras.getString("DUDE")) 1013 .isEqualTo("SWEET"); 1014 } 1015 assertComposingSpan(CharSequence text, int start, int end)1016 private void assertComposingSpan(CharSequence text, int start, int end) { 1017 assertThat(text).isInstanceOf(Spannable.class); 1018 Spannable sp = (Spannable) text; 1019 assertThat(BaseInputConnection.getComposingSpanStart(sp)).isEqualTo(start); 1020 assertThat(BaseInputConnection.getComposingSpanEnd(sp)).isEqualTo(end); 1021 } 1022 assertNoComposingSpan(CharSequence text)1023 private void assertNoComposingSpan(CharSequence text) { 1024 if (text instanceof Spannable) { 1025 assertThat(BaseInputConnection.getComposingSpanStart((Spannable) text)).isLessThan(0); 1026 } 1027 } 1028 1029 // TODO(b/123540602): add moar test cases for different sessions: 1030 // - session1 on rootView, session2 on children 1031 // - session1 on rootView, session2 on child1, session3 on child2 1032 // - combination above where the CTS test explicitly finishes a session 1033 1034 // TODO(b/123540602): add moar test cases for different scenarios, like: 1035 // - dynamically adding / 1036 // - removing views 1037 // - pausing / resuming activity / tapping home 1038 // - changing text 1039 // - secure flag with child sessions 1040 // - making sure events are flushed when activity pause / resume 1041 1042 // TODO(b/126262658): moar lifecycle events, like multiple activities. 1043 1044 } 1045