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