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 = removeUnexpectedEvents(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 unexpected 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 */ removeUnexpectedEvents( @onNull List<ContentCaptureEvent> events)317 public static List<ContentCaptureEvent> removeUnexpectedEvents( 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 /** Gets a list of events that are related to view changes only. */ getViewLevelEvents( @onNull List<ContentCaptureEvent> events)328 public static List<ContentCaptureEvent> getViewLevelEvents( 329 @NonNull List<ContentCaptureEvent> events) { 330 return Collections.unmodifiableList(events).stream().filter( 331 e -> e.getType() == TYPE_WINDOW_BOUNDS_CHANGED 332 || e.getType() == TYPE_VIEW_INSETS_CHANGED 333 || e.getType() == TYPE_VIEW_TREE_APPEARING 334 || e.getType() == TYPE_VIEW_TREE_APPEARED 335 ).collect(Collectors.toList()); 336 } 337 338 /** 339 * Asserts that a session for the given activity has events at all. 340 */ assertNoEvents(@onNull Session session, @NonNull ComponentName componentName)341 public static void assertNoEvents(@NonNull Session session, 342 @NonNull ComponentName componentName) { 343 assertRightActivity(session, session.id, componentName); 344 assertThat(session.getEvents()).isEmpty(); 345 } 346 347 /** 348 * Asserts that the events received by the service optionally contains the 349 * {@code TYPE_VIEW_DISAPPEARED} events, as they might have not been generated if the views 350 * disappeared after the activity stopped. 351 * 352 * @param events events received by the service. 353 * @param minimumSize size of events received if activity stopped before views disappeared 354 * @param expectedIds ids of views that might have disappeared. 355 * 356 * @return whether the view disappeared events were generated 357 */ 358 // TODO(b/123540067, 122315042): remove this method if we could make it deterministic, and 359 // inline the assertions (or rename / change its logic) assertViewsOptionallyDisappeared( @onNull List<ContentCaptureEvent> events, int minimumSize, @NonNull AutofillId... expectedIds)360 public static boolean assertViewsOptionallyDisappeared( 361 @NonNull List<ContentCaptureEvent> events, int minimumSize, 362 @NonNull AutofillId... expectedIds) { 363 final int actualSize = events.size(); 364 final int disappearedEventIndex; 365 if (actualSize == minimumSize) { 366 // Activity stopped before TYPE_VIEW_DISAPPEARED were sent. 367 return false; 368 } else if (actualSize == minimumSize + 1) { 369 // Activity did not receive TYPE_VIEW_TREE_APPEARING and TYPE_VIEW_TREE_APPEARED. 370 disappearedEventIndex = minimumSize; 371 } else { 372 disappearedEventIndex = minimumSize + 1; 373 } 374 final ContentCaptureEvent batchDisappearEvent = events.get(disappearedEventIndex); 375 376 if (expectedIds.length == 1) { 377 assertWithMessage("Should have just one deleted id on %s", batchDisappearEvent) 378 .that(batchDisappearEvent.getIds()).isNull(); 379 assertWithMessage("wrong deleted id on %s", batchDisappearEvent) 380 .that(batchDisappearEvent.getId()).isEqualTo(expectedIds[0]); 381 } else { 382 assertWithMessage("Should not have individual deleted id on %s", batchDisappearEvent) 383 .that(batchDisappearEvent.getId()).isNull(); 384 final List<AutofillId> actualIds = batchDisappearEvent.getIds(); 385 assertWithMessage("wrong deleteds id on %s", batchDisappearEvent) 386 .that(actualIds).containsExactly((Object[]) expectedIds); 387 } 388 return true; 389 } 390 391 /** 392 * Asserts the contents of a {@link #TYPE_VIEW_APPEARED} event, without checking for parent 393 */ assertViewWithUnknownParentAppeared( @onNull List<ContentCaptureEvent> events, int index, @NonNull ContentCaptureSessionId expectedSessionId, @NonNull View expectedView)394 public static void assertViewWithUnknownParentAppeared( 395 @NonNull List<ContentCaptureEvent> events, int index, 396 @NonNull ContentCaptureSessionId expectedSessionId, @NonNull View expectedView) { 397 assertViewWithUnknownParentAppeared(events, index, expectedView); 398 assertSessionId(expectedSessionId, expectedView); 399 } 400 401 /** 402 * Asserts the contents of a {@link #TYPE_VIEW_DISAPPEARED} event for a single view. 403 */ assertViewDisappeared(@onNull List<ContentCaptureEvent> events, int index, @NonNull AutofillId expectedId)404 public static void assertViewDisappeared(@NonNull List<ContentCaptureEvent> events, int index, 405 @NonNull AutofillId expectedId) { 406 final ContentCaptureEvent event = assertCommonViewDisappearedProperties(events, index); 407 assertWithMessage("wrong autofillId on event %s (index %s)", event, index) 408 .that(event.getId()).isEqualTo(expectedId); 409 assertWithMessage("event %s (index %s) should not have autofillIds", event, index) 410 .that(event.getIds()).isNull(); 411 } 412 413 /** 414 * Asserts the contents of a {@link #TYPE_VIEW_DISAPPEARED} event for multiple views. 415 */ assertViewsDisappeared(@onNull List<ContentCaptureEvent> events, int index, @NonNull AutofillId... expectedIds)416 public static void assertViewsDisappeared(@NonNull List<ContentCaptureEvent> events, int index, 417 @NonNull AutofillId... expectedIds) { 418 final ContentCaptureEvent event = assertCommonViewDisappearedProperties(events, index); 419 final List<AutofillId> ids = event.getIds(); 420 assertWithMessage("no autofillIds on event %s (index %s)", event, index).that(ids) 421 .isNotNull(); 422 assertWithMessage("wrong autofillId on event %s (index %s)", event, index) 423 .that(ids).containsExactly((Object[]) expectedIds).inOrder(); 424 assertWithMessage("event %s (index %s) should not have autofillId", event, index) 425 .that(event.getId()).isNull(); 426 } 427 assertCommonViewDisappearedProperties( @onNull List<ContentCaptureEvent> events, int index)428 private static ContentCaptureEvent assertCommonViewDisappearedProperties( 429 @NonNull List<ContentCaptureEvent> events, int index) { 430 final ContentCaptureEvent event = getEvent(events, index, TYPE_VIEW_DISAPPEARED); 431 assertWithMessage("event %s (index %s) should not have a ViewNode", event, index) 432 .that(event.getViewNode()).isNull(); 433 assertWithMessage("event %s (index %s) should not have text", event, index) 434 .that(event.getText()).isNull(); 435 return event; 436 } 437 438 /** 439 * Asserts the contents of a {@link #TYPE_VIEW_APPEARED} event for a virtual node. 440 */ assertVirtualViewAppeared(@onNull List<ContentCaptureEvent> events, int index, @NonNull ContentCaptureSession session, @NonNull AutofillId parentId, int childId, @Nullable String expectedText)441 public static void assertVirtualViewAppeared(@NonNull List<ContentCaptureEvent> events, 442 int index, @NonNull ContentCaptureSession session, @NonNull AutofillId parentId, 443 int childId, @Nullable String expectedText) { 444 final ContentCaptureEvent event = getEvent(events, index, TYPE_VIEW_APPEARED); 445 final ViewNode node = event.getViewNode(); 446 assertThat(node).isNotNull(); 447 final AutofillId expectedId = session.newAutofillId(parentId, childId); 448 assertWithMessage("wrong autofill id on %s (index %s)", event, index) 449 .that(node.getAutofillId()).isEqualTo(expectedId); 450 if (expectedText != null) { 451 assertWithMessage("wrong text on %s(index %s) ", event, index) 452 .that(node.getText().toString()).isEqualTo(expectedText); 453 } else { 454 assertWithMessage("%s (index %s) should not have text", node, index) 455 .that(node.getText()).isNull(); 456 } 457 } 458 459 /** 460 * Asserts the contents of a {@link #TYPE_VIEW_DISAPPEARED} event for a virtual node. 461 */ assertVirtualViewDisappeared(@onNull List<ContentCaptureEvent> events, int index, @NonNull AutofillId parentId, @NonNull ContentCaptureSession session, long childId)462 public static void assertVirtualViewDisappeared(@NonNull List<ContentCaptureEvent> events, 463 int index, @NonNull AutofillId parentId, @NonNull ContentCaptureSession session, 464 long childId) { 465 assertViewDisappeared(events, index, session.newAutofillId(parentId, childId)); 466 } 467 468 /** 469 * Asserts the contents of a {@link #TYPE_VIEW_DISAPPEARED} event for many virtual nodes. 470 */ assertVirtualViewsDisappeared(@onNull List<ContentCaptureEvent> events, int index, @NonNull AutofillId parentId, @NonNull ContentCaptureSession session, long... childrenIds)471 public static void assertVirtualViewsDisappeared(@NonNull List<ContentCaptureEvent> events, 472 int index, @NonNull AutofillId parentId, @NonNull ContentCaptureSession session, 473 long... childrenIds) { 474 final int size = childrenIds.length; 475 final AutofillId[] expectedIds = new AutofillId[size]; 476 for (int i = 0; i < childrenIds.length; i++) { 477 expectedIds[i] = session.newAutofillId(parentId, childrenIds[i]); 478 } 479 assertViewsDisappeared(events, index, expectedIds); 480 } 481 482 /** 483 * Asserts a view has the given session id. 484 */ assertSessionId(@onNull ContentCaptureSessionId expectedSessionId, @NonNull View view)485 public static void assertSessionId(@NonNull ContentCaptureSessionId expectedSessionId, 486 @NonNull View view) { 487 assertThat(expectedSessionId).isNotNull(); 488 final ContentCaptureSession session = view.getContentCaptureSession(); 489 assertWithMessage("no session for view %s", view).that(session).isNotNull(); 490 assertWithMessage("wrong session id for for view %s", view) 491 .that(session.getContentCaptureSessionId()).isEqualTo(expectedSessionId); 492 } 493 494 /** 495 * Asserts the contents of a {@link #TYPE_VIEW_TEXT_CHANGED} event. 496 */ assertViewTextChanged(@onNull List<ContentCaptureEvent> events, int index, @NonNull AutofillId expectedId, @NonNull String expectedText)497 public static void assertViewTextChanged(@NonNull List<ContentCaptureEvent> events, int index, 498 @NonNull AutofillId expectedId, @NonNull String expectedText) { 499 final ContentCaptureEvent event = getEvent(events, index, TYPE_VIEW_TEXT_CHANGED); 500 assertWithMessage("Wrong id on %s (%s)", event, index).that(event.getId()) 501 .isEqualTo(expectedId); 502 assertWithMessage("Wrong text on %s (%s)", event, index).that(event.getText().toString()) 503 .isEqualTo(expectedText); 504 } 505 506 /** 507 * Asserts the existence and contents of a {@link #TYPE_VIEW_INSETS_CHANGED} event. 508 */ assertViewInsetsChanged(@onNull List<ContentCaptureEvent> events)509 public static void assertViewInsetsChanged(@NonNull List<ContentCaptureEvent> events) { 510 boolean insetsEventFound = false; 511 for (ContentCaptureEvent event : events) { 512 if (event.getType() == TYPE_VIEW_INSETS_CHANGED) { 513 assertWithMessage("Expected view insets to be non-null on %s", event) 514 .that(event.getInsets()).isNotNull(); 515 insetsEventFound = true; 516 } 517 } 518 519 if (!insetsEventFound) { 520 throw new RetryableException( 521 String.format( 522 "Expected at least one VIEW_INSETS_CHANGED event in the set of events %s", 523 events)); 524 } 525 } 526 527 /** 528 * Asserts the existence and contents of a {@link #TYPE_WINDOW_BOUNDS_CHANGED} event. 529 */ assertWindowBoundsChanged(@onNull List<ContentCaptureEvent> events)530 public static void assertWindowBoundsChanged(@NonNull List<ContentCaptureEvent> events) { 531 boolean boundsEventFound = false; 532 for (ContentCaptureEvent event : events) { 533 if (event.getType() == TYPE_WINDOW_BOUNDS_CHANGED) { 534 assertWithMessage("Expected window bounds to be non-null on %s", event) 535 .that(event.getBounds()).isNotNull(); 536 boundsEventFound = true; 537 } 538 } 539 540 if (!boundsEventFound) { 541 throw new RetryableException( 542 String.format( 543 "Expected at least one WINDOW_BOUNDS_CHANGED event in the set of events %s", 544 events)); 545 } 546 } 547 548 /** 549 * Asserts the basic contents of a {@link #TYPE_CONTEXT_UPDATED} event. 550 */ assertContextUpdated( @onNull List<ContentCaptureEvent> events, int index)551 public static ContentCaptureEvent assertContextUpdated( 552 @NonNull List<ContentCaptureEvent> events, int index) { 553 final ContentCaptureEvent event = getEvent(events, index, TYPE_CONTEXT_UPDATED); 554 assertWithMessage("event %s (index %s) should not have a ViewNode", event, index) 555 .that(event.getViewNode()).isNull(); 556 assertWithMessage("event %s (index %s) should not have text", event, index) 557 .that(event.getViewNode()).isNull(); 558 assertWithMessage("event %s (index %s) should not have an autofillId", event, index) 559 .that(event.getId()).isNull(); 560 assertWithMessage("event %s (index %s) should not have autofillIds", event, index) 561 .that(event.getIds()).isNull(); 562 return event; 563 } 564 565 /** 566 * Asserts the order a session was created or destroyed. 567 */ assertLifecycleOrder(int expectedOrder, @NonNull Session session, @NonNull LifecycleOrder type)568 public static void assertLifecycleOrder(int expectedOrder, @NonNull Session session, 569 @NonNull LifecycleOrder type) { 570 switch(type) { 571 case CREATION: 572 assertWithMessage("Wrong order of creation for session %s", session) 573 .that(session.creationOrder).isEqualTo(expectedOrder); 574 break; 575 case DESTRUCTION: 576 assertWithMessage("Wrong order of destruction for session %s", session) 577 .that(session.destructionOrder).isEqualTo(expectedOrder); 578 break; 579 default: 580 throw new IllegalArgumentException("Invalid type: " + type); 581 } 582 } 583 584 /** 585 * Gets the event at the given index, failing with the user-friendly message if necessary... 586 */ 587 @NonNull getEvent(@onNull List<ContentCaptureEvent> events, int index, int expectedType)588 private static ContentCaptureEvent getEvent(@NonNull List<ContentCaptureEvent> events, 589 int index, int expectedType) { 590 assertWithMessage("events is null").that(events).isNotNull(); 591 final ContentCaptureEvent event = events.get(index); 592 assertWithMessage("no event at index %s (size %s): %s", index, events.size(), events) 593 .that(event).isNotNull(); 594 final int actualType = event.getType(); 595 if (actualType != expectedType) { 596 throw new AssertionError(String.format( 597 "wrong event type (expected %s, actual is %s) at index %s: %s", 598 eventTypeAsString(expectedType), eventTypeAsString(actualType), index, event)); 599 } 600 assertWithMessage("invalid time on %s (index %s)", event, index).that(event.getEventTime()) 601 .isAtLeast(MY_EPOCH); 602 return event; 603 } 604 605 /** 606 * Gets an user-friendly description of the given event type. 607 */ 608 @NonNull eventTypeAsString(int type)609 public static String eventTypeAsString(int type) { 610 final String string; 611 switch (type) { 612 case TYPE_VIEW_APPEARED: 613 string = "APPEAR"; 614 break; 615 case TYPE_VIEW_DISAPPEARED: 616 string = "DISAPPEAR"; 617 break; 618 case TYPE_VIEW_TEXT_CHANGED: 619 string = "TEXT_CHANGE"; 620 break; 621 case TYPE_VIEW_TREE_APPEARING: 622 string = "TREE_START"; 623 break; 624 case TYPE_VIEW_TREE_APPEARED: 625 string = "TREE_END"; 626 break; 627 case TYPE_SESSION_PAUSED: 628 string = "PAUSED"; 629 break; 630 case TYPE_SESSION_RESUMED: 631 string = "RESUMED"; 632 break; 633 case TYPE_WINDOW_BOUNDS_CHANGED: 634 string = "WINDOW_BOUNDS"; 635 break; 636 default: 637 return "UNKNOWN-" + type; 638 } 639 return String.format("%s-%d", string, type); 640 } 641 Assertions()642 private Assertions() { 643 throw new UnsupportedOperationException("contain static methods only"); 644 } 645 646 public enum LifecycleOrder { 647 CREATION, DESTRUCTION 648 } 649 } 650