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