• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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