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