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