• 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 package android.contentcaptureservice.cts;
17 
18 import static android.contentcaptureservice.cts.Helper.MY_EPOCH;
19 import static android.contentcaptureservice.cts.Helper.TAG;
20 import static android.view.contentcapture.ContentCaptureEvent.TYPE_CONTEXT_UPDATED;
21 import static android.view.contentcapture.ContentCaptureEvent.TYPE_SESSION_PAUSED;
22 import static android.view.contentcapture.ContentCaptureEvent.TYPE_SESSION_RESUMED;
23 import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_APPEARED;
24 import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_DISAPPEARED;
25 import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_INSETS_CHANGED;
26 import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_TEXT_CHANGED;
27 import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_TREE_APPEARED;
28 import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_TREE_APPEARING;
29 import static android.view.contentcapture.ContentCaptureEvent.TYPE_WINDOW_BOUNDS_CHANGED;
30 import static android.view.contentcapture.ContentCaptureEvent.TYPE_SESSION_FLUSH;
31 
32 import static com.google.common.truth.Truth.assertThat;
33 import static com.google.common.truth.Truth.assertWithMessage;
34 
35 import android.app.assist.ActivityId;
36 import android.content.ComponentName;
37 import android.content.LocusId;
38 import android.contentcaptureservice.cts.CtsContentCaptureService.Session;
39 import android.util.Log;
40 import android.view.Display;
41 import android.view.View;
42 import android.view.autofill.AutofillId;
43 import android.view.contentcapture.ContentCaptureEvent;
44 import android.view.contentcapture.ContentCaptureSession;
45 import android.view.contentcapture.ContentCaptureSessionId;
46 import android.view.contentcapture.ViewNode;
47 
48 import androidx.annotation.NonNull;
49 import androidx.annotation.Nullable;
50 
51 import com.android.compatibility.common.util.RetryableException;
52 
53 import java.util.Collections;
54 import java.util.List;
55 import java.util.stream.Collectors;
56 
57 /**
58  * Helper for common assertions.
59  */
60 final class Assertions {
61 
62     /**
63      * Asserts a session belongs to the right activity.
64      */
assertRightActivity(@onNull Session session, @NonNull ContentCaptureSessionId expectedSessionId, @NonNull AbstractContentCaptureActivity activity)65     public static void assertRightActivity(@NonNull Session session,
66             @NonNull ContentCaptureSessionId expectedSessionId,
67             @NonNull AbstractContentCaptureActivity activity) {
68         assertRightActivity(session, expectedSessionId, activity.getComponentName());
69     }
70 
71     /**
72      * Asserts a session belongs to the right activity.
73      */
assertRightActivity(@onNull Session session, @NonNull ContentCaptureSessionId expectedSessionId, @NonNull ComponentName componentName)74     public static void assertRightActivity(@NonNull Session session,
75             @NonNull ContentCaptureSessionId expectedSessionId,
76             @NonNull ComponentName componentName) {
77         assertWithMessage("wrong activity for %s", session)
78                 .that(session.context.getActivityComponent()).isEqualTo(componentName);
79         // TODO(b/123540602): merge both or replace check above by:
80         //  assertMainSessionContext(session, activity);
81         assertThat(session.id).isEqualTo(expectedSessionId);
82     }
83 
84     /**
85      * Asserts the context of a main session.
86      */
assertMainSessionContext(@onNull Session session, @NonNull AbstractContentCaptureActivity activity)87     public static void assertMainSessionContext(@NonNull Session session,
88             @NonNull AbstractContentCaptureActivity activity) {
89         assertMainSessionContext(session, activity, /* expectedFlags= */ 0);
90     }
91 
92     /**
93      * Asserts the context of a main session.
94      */
assertMainSessionContext(@onNull Session session, @NonNull AbstractContentCaptureActivity activity, int expectedFlags)95     public static void assertMainSessionContext(@NonNull Session session,
96             @NonNull AbstractContentCaptureActivity activity, int expectedFlags) {
97         assertWithMessage("no context on %s", session).that(session.context).isNotNull();
98         assertWithMessage("wrong activity for %s", session)
99                 .that(session.context.getActivityComponent())
100                 .isEqualTo(activity.getComponentName());
101         assertWithMessage("context for session %s should have displayId", session)
102                 .that(session.context.getDisplayId()).isNotEqualTo(Display.INVALID_DISPLAY);
103         assertWithMessage("wrong task id for session %s", session)
104                 .that(session.context.getTaskId()).isEqualTo(activity.getRealTaskId());
105         assertWithMessage("wrong flags on context for session %s", session)
106                 .that(session.context.getFlags()).isEqualTo(expectedFlags);
107         assertWithMessage("context for session %s should not have ID", session)
108                 .that(session.context.getLocusId()).isNull();
109         assertWithMessage("context for session %s should not have extras", session)
110                 .that(session.context.getExtras()).isNull();
111         final ActivityId activityId = session.context.getActivityId();
112         assertWithMessage("context for session %s should have ActivityIds", session)
113                 .that(activityId).isNotNull();
114         assertWithMessage("wrong task id for session %s", session)
115                 .that(activityId.getTaskId()).isEqualTo(activity.getRealTaskId());
116         assertWithMessage("context for session %s should have ActivityId", session)
117                 .that(activityId.getToken()).isNotNull();
118         assertWithMessage("context for session %s should have windowToken", session)
119                 .that(session.context.getWindowToken()).isNotNull();
120     }
121 
122     /**
123      * Asserts the invariants of a child session.
124      */
assertChildSessionContext(@onNull Session session)125     public static void assertChildSessionContext(@NonNull Session session) {
126         assertWithMessage("no context on %s", session).that(session.context).isNotNull();
127         assertWithMessage("context for session %s should not have component", session)
128                 .that(session.context.getActivityComponent()).isNull();
129         assertWithMessage("context for session %s should not have displayId", session)
130                 .that(session.context.getDisplayId()).isEqualTo(Display.INVALID_DISPLAY);
131         assertWithMessage("context for session %s should not have taskId", session)
132                 .that(session.context.getTaskId()).isEqualTo(0);
133         assertWithMessage("context for session %s should not have flags", session)
134                 .that(session.context.getFlags()).isEqualTo(0);
135         final ActivityId activityId = session.context.getActivityId();
136         assertWithMessage("context for session %s should not have ActivityIds", session)
137                 .that(activityId).isNull();
138         assertWithMessage("context for session %s should not have windowToken", session)
139                 .that(session.context.getWindowToken()).isNull();
140     }
141 
142     /**
143      * Asserts the invariants of a child session.
144      */
assertChildSessionContext(@onNull Session session, @NonNull String expectedId)145     public static void assertChildSessionContext(@NonNull Session session,
146             @NonNull String expectedId) {
147         assertChildSessionContext(session);
148         assertThat(session.context.getLocusId()).isEqualTo(new LocusId(expectedId));
149     }
150 
151     /**
152      * Asserts a session belongs to the right parent
153      */
assertRightRelationship(@onNull Session parent, @NonNull Session child)154     public static void assertRightRelationship(@NonNull Session parent, @NonNull Session child) {
155         final ContentCaptureSessionId expectedParentId = parent.id;
156         assertWithMessage("No id on parent session %s", parent).that(expectedParentId).isNotNull();
157         assertWithMessage("No context on child session %s", child).that(child.context).isNotNull();
158         final ContentCaptureSessionId actualParentId = child.context.getParentSessionId();
159         assertWithMessage("No parent id on context %s of child session %s", child.context, child)
160                 .that(actualParentId).isNotNull();
161         assertWithMessage("id of parent session doesn't match child").that(actualParentId)
162                 .isEqualTo(expectedParentId);
163     }
164 
165     /**
166      * Asserts the contents of a {@link #TYPE_VIEW_APPEARED} event, without checking for parent id.
167      */
assertViewWithUnknownParentAppeared( @onNull List<ContentCaptureEvent> events, int index, @NonNull View expectedView)168     public static ViewNode assertViewWithUnknownParentAppeared(
169             @NonNull List<ContentCaptureEvent> events, int index, @NonNull View expectedView) {
170         return assertViewWithUnknownParentAppeared(events, index, expectedView,
171                 /* expectedText= */ null);
172     }
173 
174     /**
175      * Asserts the contents of a {@link #TYPE_VIEW_APPEARED} event for a decor view.
176      *
177      * <P>The decor view is typically internal, so there isn't much we can assert, other than its
178      * autofill id.
179      */
assertDecorViewAppeared(@onNull List<ContentCaptureEvent> events, int index, @NonNull View expectedDecorView)180     public static void assertDecorViewAppeared(@NonNull List<ContentCaptureEvent> events,
181             int index, @NonNull View expectedDecorView) {
182         final ContentCaptureEvent event = assertViewAppeared(events, index);
183         assertWithMessage("wrong autofill id on %s (%s)", event, index)
184                 .that(event.getViewNode().getAutofillId())
185                 .isEqualTo(expectedDecorView.getAutofillId());
186     }
187 
188     /**
189      * Asserts the contents of a {@link #TYPE_VIEW_APPEARED} event, without checking for parent id.
190      */
assertViewWithUnknownParentAppeared( @onNull List<ContentCaptureEvent> events, int index, @NonNull View expectedView, @Nullable String expectedText)191     public static ViewNode assertViewWithUnknownParentAppeared(
192             @NonNull List<ContentCaptureEvent> events, int index, @NonNull View expectedView,
193             @Nullable String expectedText) {
194         final ContentCaptureEvent event = assertViewAppeared(events, index);
195         final ViewNode node = event.getViewNode();
196 
197         assertWithMessage("wrong class on %s (%s)", event, index).that(node.getClassName())
198                 .isEqualTo(expectedView.getClass().getName());
199         assertWithMessage("wrong autofill id on %s (%s)", event, index).that(node.getAutofillId())
200                 .isEqualTo(expectedView.getAutofillId());
201 
202         if (expectedText != null) {
203             assertWithMessage("wrong text on %s (%s)", event, index).that(node.getText().toString())
204                     .isEqualTo(expectedText);
205         }
206         // TODO(b/123540602): test more fields, like resource id
207         return node;
208     }
209 
210     /**
211      * Asserts the contents of a {@link #TYPE_VIEW_APPEARED} event, without checking for parent id.
212      */
assertViewAppeared(@onNull List<ContentCaptureEvent> events, int index)213     public static ContentCaptureEvent assertViewAppeared(@NonNull List<ContentCaptureEvent> events,
214             int index) {
215         final ContentCaptureEvent event = getEvent(events, index, TYPE_VIEW_APPEARED);
216         final ViewNode node = event.getViewNode();
217         assertThat(node).isNotNull();
218         return event;
219     }
220 
221     /**
222      * Asserts the contents of a {@link #TYPE_VIEW_APPEARED} event.
223      */
assertViewAppeared(@onNull List<ContentCaptureEvent> events, int index, @NonNull View expectedView, @Nullable AutofillId expectedParentId, @Nullable String expectedText)224     public static void assertViewAppeared(@NonNull List<ContentCaptureEvent> events, int index,
225             @NonNull View expectedView, @Nullable AutofillId expectedParentId,
226             @Nullable String expectedText) {
227         final ViewNode node = assertViewWithUnknownParentAppeared(events, index, expectedView,
228                 expectedText);
229         assertWithMessage("wrong parent autofill id on %s (%s)", events.get(index), index)
230             .that(node.getParentAutofillId()).isEqualTo(expectedParentId);
231     }
232 
233     /**
234      * Asserts the contents of a {@link #TYPE_VIEW_APPEARED} event.
235      */
assertViewAppeared(@onNull List<ContentCaptureEvent> events, int index, @NonNull View expectedView, @Nullable AutofillId expectedParentId)236     public static void assertViewAppeared(@NonNull List<ContentCaptureEvent> events, int index,
237             @NonNull View expectedView, @Nullable AutofillId expectedParentId) {
238         assertViewAppeared(events, index, expectedView, expectedParentId, /* expectedText= */ null);
239     }
240 
241     /**
242      * Asserts the contents of a {@link #TYPE_VIEW_APPEARED} event.
243      */
assertViewAppeared(@onNull List<ContentCaptureEvent> events, int index, @NonNull ContentCaptureSessionId expectedSessionId, @NonNull View expectedView, @Nullable AutofillId expectedParentId)244     public static void assertViewAppeared(@NonNull List<ContentCaptureEvent> events, int index,
245             @NonNull ContentCaptureSessionId expectedSessionId,
246             @NonNull View expectedView, @Nullable AutofillId expectedParentId) {
247         assertViewAppeared(events, index, expectedView, expectedParentId);
248         assertSessionId(expectedSessionId, expectedView);
249     }
250 
251     /**
252      * Asserts the contents of a {@link #TYPE_VIEW_TREE_APPEARING} event.
253      */
assertViewTreeStarted(@onNull List<ContentCaptureEvent> events, int index)254     public static void assertViewTreeStarted(@NonNull List<ContentCaptureEvent> events,
255             int index) {
256         assertSessionLevelEvent(events, index, TYPE_VIEW_TREE_APPEARING);
257     }
258 
259     /**
260      * Asserts the contents of a {@link #TYPE_VIEW_TREE_APPEARED} event.
261      */
assertViewTreeFinished(@onNull List<ContentCaptureEvent> events, int index)262     public static void assertViewTreeFinished(@NonNull List<ContentCaptureEvent> events,
263             int index) {
264         assertSessionLevelEvent(events, index, TYPE_VIEW_TREE_APPEARED);
265     }
266 
assertSessionLevelEvent(@onNull List<ContentCaptureEvent> events, int index, int expectedType)267     private static void assertSessionLevelEvent(@NonNull List<ContentCaptureEvent> events,
268             int index, int expectedType) {
269         final ContentCaptureEvent event = getEvent(events, index, expectedType);
270         assertWithMessage("event %s (index %s) should not have a ViewNode", event, index)
271                 .that(event.getViewNode()).isNull();
272         assertWithMessage("event %s (index %s) should not have text", event, index)
273                 .that(event.getText()).isNull();
274         assertWithMessage("event %s (index %s) should not have an autofillId", event, index)
275                 .that(event.getId()).isNull();
276         assertWithMessage("event %s (index %s) should not have autofillIds", event, index)
277                 .that(event.getIds()).isNull();
278     }
279 
280     /**
281      * Asserts the contents of a {@link #TYPE_SESSION_RESUMED} event.
282      */
assertSessionResumed(@onNull List<ContentCaptureEvent> events, int index)283     public static void assertSessionResumed(@NonNull List<ContentCaptureEvent> events,
284             int index) {
285         assertSessionLevelEvent(events, index, TYPE_SESSION_RESUMED);
286     }
287 
288     /**
289      * Asserts the contents of a {@link #TYPE_SESSION_PAUSED} event.
290      */
assertSessionPaused(@onNull List<ContentCaptureEvent> events, int index)291     public static void assertSessionPaused(@NonNull List<ContentCaptureEvent> events,
292             int index) {
293         assertSessionLevelEvent(events, index, TYPE_SESSION_PAUSED);
294     }
295 
296     /**
297      * Asserts the contents of a {@link #TYPE_SESSION_FLUSH} event.
298      */
assertSessionFlush(@onNull List<ContentCaptureEvent> events, int index)299     public static void assertSessionFlush(@NonNull List<ContentCaptureEvent> events,
300             int index) {
301         assertSessionLevelEvent(events, index, TYPE_SESSION_FLUSH);
302     }
303 
304     /**
305      * Asserts that a session for the given activity has no view-level events, just
306      * {@link #TYPE_SESSION_RESUMED} and {@link #TYPE_SESSION_PAUSED}.
307      */
assertNoViewLevelEvents(@onNull Session session, @NonNull AbstractContentCaptureActivity activity)308     public static void assertNoViewLevelEvents(@NonNull Session session,
309             @NonNull AbstractContentCaptureActivity activity) {
310         assertRightActivity(session, session.id, activity);
311         final List<ContentCaptureEvent> events = removeUnexpectedEvents(session.getEvents());
312         Log.v(TAG, "events on " + activity + ": " + events);
313         assertThat(events).hasSize(2);
314         assertSessionResumed(events, 0);
315         assertSessionPaused(events, 1);
316     }
317 
318     /**
319      * This method is used to remove unexpected events if the test should only
320      * contain session level events.
321      * In special case, there are some events, such as {@link #TYPE_WINDOW_BOUNDS_CHANGED}
322      * and {@link #TYPE_VIEW_INSETS_CHANGED}, will be accidentally generated by
323      * the system. Because these events were not expected in the test, remove
324      * them if needed.
325      */
removeUnexpectedEvents( @onNull List<ContentCaptureEvent> events)326     public static List<ContentCaptureEvent> removeUnexpectedEvents(
327             @NonNull List<ContentCaptureEvent> events) {
328         return Collections.unmodifiableList(events).stream().filter(
329                 e -> e.getType() != TYPE_WINDOW_BOUNDS_CHANGED
330                         && e.getType() != TYPE_VIEW_INSETS_CHANGED
331                         && e.getType() != TYPE_VIEW_TREE_APPEARING
332                         && e.getType() != TYPE_VIEW_TREE_APPEARED
333         ).collect(Collectors.toList());
334     }
335 
336     /** Gets a list of events that are related to view changes only. */
getViewLevelEvents( @onNull List<ContentCaptureEvent> events)337     public static List<ContentCaptureEvent> getViewLevelEvents(
338             @NonNull List<ContentCaptureEvent> events) {
339         return Collections.unmodifiableList(events).stream().filter(
340                 e -> e.getType() == TYPE_WINDOW_BOUNDS_CHANGED
341                         || e.getType() == TYPE_VIEW_INSETS_CHANGED
342                         || e.getType() == TYPE_VIEW_TREE_APPEARING
343                         || e.getType() == TYPE_VIEW_TREE_APPEARED
344         ).collect(Collectors.toList());
345     }
346 
347     /**
348      * Asserts that a session for the given activity has events at all.
349      */
assertNoEvents(@onNull Session session, @NonNull ComponentName componentName)350     public static void assertNoEvents(@NonNull Session session,
351             @NonNull ComponentName componentName) {
352         assertRightActivity(session, session.id, componentName);
353         assertThat(session.getEvents()).isEmpty();
354     }
355 
356     /**
357      * Asserts that the events received by the service optionally contains the
358      * {@code TYPE_VIEW_DISAPPEARED} events, as they might have not been generated if the views
359      * disappeared after the activity stopped.
360      *
361      * @param events events received by the service.
362      * @param minimumSize size of events received if activity stopped before views disappeared
363      * @param expectedIds ids of views that might have disappeared.
364      *
365      * @return whether the view disappeared events were generated
366      */
367     // TODO(b/123540067, 122315042): remove this method if we could make it deterministic, and
368     // inline the assertions (or rename / change its logic)
assertViewsOptionallyDisappeared( @onNull List<ContentCaptureEvent> events, int minimumSize, @NonNull AutofillId... expectedIds)369     public static boolean assertViewsOptionallyDisappeared(
370             @NonNull List<ContentCaptureEvent> events, int minimumSize,
371             @NonNull AutofillId... expectedIds) {
372         final int actualSize = events.size();
373         final int disappearedEventIndex;
374         if (actualSize == minimumSize) {
375             // Activity stopped before TYPE_VIEW_DISAPPEARED were sent.
376             return false;
377         } else if (actualSize == minimumSize + 1) {
378             // Activity did not receive TYPE_VIEW_TREE_APPEARING and TYPE_VIEW_TREE_APPEARED.
379             disappearedEventIndex = minimumSize;
380         } else {
381             disappearedEventIndex = minimumSize + 1;
382         }
383         final ContentCaptureEvent batchDisappearEvent = events.get(disappearedEventIndex);
384 
385         if (expectedIds.length == 1) {
386             assertWithMessage("Should have just one deleted id on %s", batchDisappearEvent)
387                     .that(batchDisappearEvent.getIds()).isNull();
388             assertWithMessage("wrong deleted id on %s", batchDisappearEvent)
389                     .that(batchDisappearEvent.getId()).isEqualTo(expectedIds[0]);
390         } else {
391             assertWithMessage("Should not have individual deleted id on %s", batchDisappearEvent)
392                     .that(batchDisappearEvent.getId()).isNull();
393             final List<AutofillId> actualIds = batchDisappearEvent.getIds();
394             assertWithMessage("wrong deleteds id on %s", batchDisappearEvent)
395                     .that(actualIds).containsExactly((Object[]) expectedIds);
396         }
397         return true;
398     }
399 
400     /**
401      * Asserts the contents of a {@link #TYPE_VIEW_APPEARED} event, without checking for parent
402      */
assertViewWithUnknownParentAppeared( @onNull List<ContentCaptureEvent> events, int index, @NonNull ContentCaptureSessionId expectedSessionId, @NonNull View expectedView)403     public static void assertViewWithUnknownParentAppeared(
404             @NonNull List<ContentCaptureEvent> events, int index,
405             @NonNull ContentCaptureSessionId expectedSessionId, @NonNull View expectedView) {
406         assertViewWithUnknownParentAppeared(events, index, expectedView);
407         assertSessionId(expectedSessionId, expectedView);
408     }
409 
410     /**
411      * Asserts the contents of a {@link #TYPE_VIEW_DISAPPEARED} event for a single view.
412      */
assertViewDisappeared(@onNull List<ContentCaptureEvent> events, int index, @NonNull AutofillId expectedId)413     public static void assertViewDisappeared(@NonNull List<ContentCaptureEvent> events, int index,
414             @NonNull AutofillId expectedId) {
415         final ContentCaptureEvent event = assertCommonViewDisappearedProperties(events, index);
416         assertWithMessage("wrong autofillId on event %s (index %s)", event, index)
417             .that(event.getId()).isEqualTo(expectedId);
418         assertWithMessage("event %s (index %s) should not have autofillIds", event, index)
419             .that(event.getIds()).isNull();
420     }
421 
422     /**
423      * Asserts the contents of a {@link #TYPE_VIEW_DISAPPEARED} event for multiple views.
424      */
assertViewsDisappeared(@onNull List<ContentCaptureEvent> events, int index, @NonNull AutofillId... expectedIds)425     public static void assertViewsDisappeared(@NonNull List<ContentCaptureEvent> events, int index,
426             @NonNull AutofillId... expectedIds) {
427         final ContentCaptureEvent event = assertCommonViewDisappearedProperties(events, index);
428         final List<AutofillId> ids = event.getIds();
429         assertWithMessage("no autofillIds on event %s (index %s)", event, index).that(ids)
430                 .isNotNull();
431         assertWithMessage("wrong autofillId on event %s (index %s)", event, index)
432             .that(ids).containsExactly((Object[]) expectedIds).inOrder();
433         assertWithMessage("event %s (index %s) should not have autofillId", event, index)
434             .that(event.getId()).isNull();
435     }
436 
assertCommonViewDisappearedProperties( @onNull List<ContentCaptureEvent> events, int index)437     private static ContentCaptureEvent assertCommonViewDisappearedProperties(
438             @NonNull List<ContentCaptureEvent> events, int index) {
439         final ContentCaptureEvent event = getEvent(events, index, TYPE_VIEW_DISAPPEARED);
440         assertWithMessage("event %s (index %s) should not have a ViewNode", event, index)
441                 .that(event.getViewNode()).isNull();
442         assertWithMessage("event %s (index %s) should not have text", event, index)
443                 .that(event.getText()).isNull();
444         return event;
445     }
446 
447     /**
448      * Asserts the contents of a {@link #TYPE_VIEW_APPEARED} event for a virtual node.
449      */
assertVirtualViewAppeared(@onNull List<ContentCaptureEvent> events, int index, @NonNull ContentCaptureSession session, @NonNull AutofillId parentId, int childId, @Nullable String expectedText)450     public static void assertVirtualViewAppeared(@NonNull List<ContentCaptureEvent> events,
451             int index, @NonNull ContentCaptureSession session, @NonNull AutofillId parentId,
452             int childId, @Nullable String expectedText) {
453         final ContentCaptureEvent event = getEvent(events, index, TYPE_VIEW_APPEARED);
454         final ViewNode node = event.getViewNode();
455         assertThat(node).isNotNull();
456         final AutofillId expectedId = session.newAutofillId(parentId, childId);
457         assertWithMessage("wrong autofill id on %s (index %s)", event, index)
458             .that(node.getAutofillId()).isEqualTo(expectedId);
459         if (expectedText != null) {
460             assertWithMessage("wrong text on %s(index %s) ", event, index)
461                 .that(node.getText().toString()).isEqualTo(expectedText);
462         } else {
463             assertWithMessage("%s (index %s) should not have text", node, index)
464                 .that(node.getText()).isNull();
465         }
466     }
467 
468     /**
469      * Asserts the contents of a {@link #TYPE_VIEW_DISAPPEARED} event for a virtual node.
470      */
assertVirtualViewDisappeared(@onNull List<ContentCaptureEvent> events, int index, @NonNull AutofillId parentId, @NonNull ContentCaptureSession session, long childId)471     public static void assertVirtualViewDisappeared(@NonNull List<ContentCaptureEvent> events,
472             int index, @NonNull AutofillId parentId, @NonNull ContentCaptureSession session,
473             long childId) {
474         assertViewDisappeared(events, index, session.newAutofillId(parentId, childId));
475     }
476 
477     /**
478      * Asserts the contents of a {@link #TYPE_VIEW_DISAPPEARED} event for many virtual nodes.
479      */
assertVirtualViewsDisappeared(@onNull List<ContentCaptureEvent> events, int index, @NonNull AutofillId parentId, @NonNull ContentCaptureSession session, long... childrenIds)480     public static void assertVirtualViewsDisappeared(@NonNull List<ContentCaptureEvent> events,
481             int index, @NonNull AutofillId parentId, @NonNull ContentCaptureSession session,
482             long... childrenIds) {
483         final int size = childrenIds.length;
484         final AutofillId[] expectedIds = new AutofillId[size];
485         for (int i = 0; i < childrenIds.length; i++) {
486             expectedIds[i] = session.newAutofillId(parentId, childrenIds[i]);
487         }
488         assertViewsDisappeared(events, index, expectedIds);
489     }
490 
491     /**
492      * Asserts a view has the given session id.
493      */
assertSessionId(@onNull ContentCaptureSessionId expectedSessionId, @NonNull View view)494     public static void assertSessionId(@NonNull ContentCaptureSessionId expectedSessionId,
495             @NonNull View view) {
496         assertThat(expectedSessionId).isNotNull();
497         final ContentCaptureSession session = view.getContentCaptureSession();
498         assertWithMessage("no session for view %s", view).that(session).isNotNull();
499         assertWithMessage("wrong session id for for view %s", view)
500                 .that(session.getContentCaptureSessionId()).isEqualTo(expectedSessionId);
501     }
502 
503     /**
504      * Asserts the contents of a {@link #TYPE_VIEW_TEXT_CHANGED} event.
505      */
assertViewTextChanged(@onNull List<ContentCaptureEvent> events, int index, @NonNull AutofillId expectedId, @NonNull String expectedText)506     public static void assertViewTextChanged(@NonNull List<ContentCaptureEvent> events, int index,
507             @NonNull AutofillId expectedId, @NonNull String expectedText) {
508         final ContentCaptureEvent event = getEvent(events, index, TYPE_VIEW_TEXT_CHANGED);
509         assertWithMessage("Wrong id on %s (%s)", event, index).that(event.getId())
510                 .isEqualTo(expectedId);
511         assertWithMessage("Wrong text on %s (%s)", event, index).that(event.getText().toString())
512                 .isEqualTo(expectedText);
513     }
514 
515     /**
516      * Asserts the existence and contents of a {@link #TYPE_VIEW_INSETS_CHANGED} event.
517      */
assertViewInsetsChanged(@onNull List<ContentCaptureEvent> events)518     public static void assertViewInsetsChanged(@NonNull List<ContentCaptureEvent> events) {
519         boolean insetsEventFound = false;
520         for (ContentCaptureEvent event : events) {
521             if (event.getType() == TYPE_VIEW_INSETS_CHANGED) {
522                 assertWithMessage("Expected view insets to be non-null on %s", event)
523                     .that(event.getInsets()).isNotNull();
524                 insetsEventFound = true;
525             }
526         }
527 
528         if (!insetsEventFound) {
529             throw new RetryableException(
530                 String.format(
531                     "Expected at least one VIEW_INSETS_CHANGED event in the set of events %s",
532                     events));
533         }
534     }
535 
536     /**
537      * Asserts the existence and contents of a {@link #TYPE_WINDOW_BOUNDS_CHANGED} event.
538      */
assertWindowBoundsChanged(@onNull List<ContentCaptureEvent> events)539     public static void assertWindowBoundsChanged(@NonNull List<ContentCaptureEvent> events) {
540         boolean boundsEventFound = false;
541         for (ContentCaptureEvent event : events) {
542             if (event.getType() == TYPE_WINDOW_BOUNDS_CHANGED) {
543                 assertWithMessage("Expected window bounds to be non-null on %s", event)
544                         .that(event.getBounds()).isNotNull();
545                 boundsEventFound = true;
546             }
547         }
548 
549         if (!boundsEventFound) {
550             throw new RetryableException(
551                 String.format(
552                         "Expected at least one WINDOW_BOUNDS_CHANGED event in the set of events %s",
553                         events));
554         }
555     }
556 
557     /**
558      * Asserts the basic contents of a {@link #TYPE_CONTEXT_UPDATED} event.
559      */
assertContextUpdated( @onNull List<ContentCaptureEvent> events, int index)560     public static ContentCaptureEvent assertContextUpdated(
561             @NonNull List<ContentCaptureEvent> events, int index) {
562         final ContentCaptureEvent event = getEvent(events, index, TYPE_CONTEXT_UPDATED);
563         assertWithMessage("event %s (index %s) should not have a ViewNode", event, index)
564                 .that(event.getViewNode()).isNull();
565         assertWithMessage("event %s (index %s) should not have text", event, index)
566                 .that(event.getViewNode()).isNull();
567         assertWithMessage("event %s (index %s) should not have an autofillId", event, index)
568                 .that(event.getId()).isNull();
569         assertWithMessage("event %s (index %s) should not have autofillIds", event, index)
570                 .that(event.getIds()).isNull();
571         return event;
572     }
573 
574     /**
575      * Asserts the order a session was created or destroyed.
576      */
assertLifecycleOrder(int expectedOrder, @NonNull Session session, @NonNull LifecycleOrder type)577     public static void assertLifecycleOrder(int expectedOrder, @NonNull Session session,
578             @NonNull LifecycleOrder type) {
579         switch(type) {
580             case CREATION:
581                 assertWithMessage("Wrong order of creation for session %s", session)
582                     .that(session.creationOrder).isEqualTo(expectedOrder);
583                 break;
584             case DESTRUCTION:
585                 assertWithMessage("Wrong order of destruction for session %s", session)
586                     .that(session.destructionOrder).isEqualTo(expectedOrder);
587                 break;
588             default:
589                 throw new IllegalArgumentException("Invalid type: " + type);
590         }
591     }
592 
593     /**
594      * Gets the event at the given index, failing with the user-friendly message if necessary...
595      */
596     @NonNull
getEvent(@onNull List<ContentCaptureEvent> events, int index, int expectedType)597     private static ContentCaptureEvent getEvent(@NonNull List<ContentCaptureEvent> events,
598             int index, int expectedType) {
599         assertWithMessage("events is null").that(events).isNotNull();
600         final ContentCaptureEvent event = events.get(index);
601         assertWithMessage("no event at index %s (size %s): %s", index, events.size(), events)
602                 .that(event).isNotNull();
603         final int actualType = event.getType();
604         if (actualType != expectedType) {
605             throw new AssertionError(String.format(
606                     "wrong event type (expected %s, actual is %s) at index %s: %s",
607                     eventTypeAsString(expectedType), eventTypeAsString(actualType), index, event));
608         }
609         assertWithMessage("invalid time on %s (index %s)", event, index).that(event.getEventTime())
610                  .isAtLeast(MY_EPOCH);
611         return event;
612     }
613 
614     /**
615      * Gets an user-friendly description of the given event type.
616      */
617     @NonNull
eventTypeAsString(int type)618     public static String eventTypeAsString(int type) {
619         final String string;
620         switch (type) {
621             case TYPE_VIEW_APPEARED:
622                 string = "APPEAR";
623                 break;
624             case TYPE_VIEW_DISAPPEARED:
625                 string = "DISAPPEAR";
626                 break;
627             case TYPE_VIEW_TEXT_CHANGED:
628                 string = "TEXT_CHANGE";
629                 break;
630             case TYPE_VIEW_TREE_APPEARING:
631                 string = "TREE_START";
632                 break;
633             case TYPE_VIEW_TREE_APPEARED:
634                 string = "TREE_END";
635                 break;
636             case TYPE_SESSION_PAUSED:
637                 string = "PAUSED";
638                 break;
639             case TYPE_SESSION_RESUMED:
640                 string = "RESUMED";
641                 break;
642             case TYPE_WINDOW_BOUNDS_CHANGED:
643                 string = "WINDOW_BOUNDS";
644                 break;
645             case TYPE_SESSION_FLUSH:
646                 string = "SESSION_FLUSH";
647                 break;
648             default:
649                 return "UNKNOWN-" + type;
650         }
651         return String.format("%s-%d", string, type);
652     }
653 
Assertions()654     private Assertions() {
655         throw new UnsupportedOperationException("contain static methods only");
656     }
657 
658     public enum LifecycleOrder {
659         CREATION, DESTRUCTION
660     }
661 }
662