1 /* <lambda>null2 * Copyright (C) 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.tools.flicker.subject.wm 18 19 import android.graphics.Region 20 import android.tools.Rotation 21 import android.tools.flicker.assertions.Fact 22 import android.tools.flicker.subject.FlickerSubject 23 import android.tools.flicker.subject.exceptions.ExceptionMessageBuilder 24 import android.tools.flicker.subject.exceptions.IncorrectVisibilityException 25 import android.tools.flicker.subject.exceptions.InvalidElementException 26 import android.tools.flicker.subject.exceptions.InvalidPropertyException 27 import android.tools.flicker.subject.exceptions.SubjectAssertionError 28 import android.tools.flicker.subject.region.RegionSubject 29 import android.tools.function.AssertionPredicate 30 import android.tools.io.Reader 31 import android.tools.traces.component.ComponentNameMatcher 32 import android.tools.traces.component.IComponentMatcher 33 import android.tools.traces.wm.WindowManagerState 34 import android.tools.traces.wm.WindowState 35 import java.util.function.Predicate 36 37 /** 38 * Subject for [WindowManagerState] objects, used to make assertions over behaviors that occur on a 39 * single WM state. 40 * 41 * To make assertions over a specific state from a trace it is recommended to create a subject using 42 * [WindowManagerTraceSubject](myTrace) and select the specific state using: 43 * ``` 44 * [WindowManagerTraceSubject.first] 45 * [WindowManagerTraceSubject.last] 46 * [WindowManagerTraceSubject.entry] 47 * ``` 48 * 49 * Alternatively, it is also possible to use [WindowManagerStateSubject](myState). 50 * 51 * Example: 52 * ``` 53 * val trace = WindowManagerTraceParser().parse(myTraceFile) 54 * val subject = WindowManagerTraceSubject(trace).first() 55 * .contains("ValidWindow") 56 * .notContains("ImaginaryWindow") 57 * .showsAboveAppWindow("NavigationBar") 58 * .invoke { myCustomAssertion(this) } 59 * ``` 60 */ 61 class WindowManagerStateSubject 62 @JvmOverloads 63 constructor( 64 val wmState: WindowManagerState, 65 override val reader: Reader? = null, 66 val trace: WindowManagerTraceSubject? = null, 67 ) : FlickerSubject(), IWindowManagerSubject<WindowManagerStateSubject, RegionSubject> { 68 override val timestamp = wmState.timestamp 69 70 val subjects by lazy { wmState.windowStates.map { WindowStateSubject(timestamp, it, reader) } } 71 72 val appWindows: List<WindowStateSubject> 73 get() = subjects.filter { wmState.appWindows.contains(it.windowState) } 74 75 val nonAppWindows: List<WindowStateSubject> 76 get() = subjects.filter { wmState.nonAppWindows.contains(it.windowState) } 77 78 val aboveAppWindows: List<WindowStateSubject> 79 get() = subjects.filter { wmState.aboveAppWindows.contains(it.windowState) } 80 81 val belowAppWindows: List<WindowStateSubject> 82 get() = subjects.filter { wmState.belowAppWindows.contains(it.windowState) } 83 84 val visibleWindows: List<WindowStateSubject> 85 get() = subjects.filter { wmState.visibleWindows.contains(it.windowState) } 86 87 val visibleAppWindows: List<WindowStateSubject> 88 get() = subjects.filter { wmState.visibleAppWindows.contains(it.windowState) } 89 90 /** Executes a custom [assertion] on the current subject */ 91 operator fun invoke( 92 assertion: AssertionPredicate<WindowManagerState> 93 ): WindowManagerStateSubject = apply { assertion.verify(this.wmState) } 94 95 /** {@inheritDoc} */ 96 override fun isEmpty(): WindowManagerStateSubject = apply { 97 check { "WM state is empty" }.that(subjects.isEmpty()).isEqual(true) 98 } 99 100 /** {@inheritDoc} */ 101 override fun isNotEmpty(): WindowManagerStateSubject = apply { 102 check { "WM state is not empty" }.that(subjects.isEmpty()).isEqual(false) 103 } 104 105 /** {@inheritDoc} */ 106 override fun visibleRegion(componentMatcher: IComponentMatcher?): RegionSubject { 107 val selectedWindows = 108 if (componentMatcher == null) { 109 // No filters so use all subjects 110 subjects 111 } else { 112 subjects.filter { componentMatcher.windowMatchesAnyOf(it.windowState) } 113 } 114 115 if (selectedWindows.isEmpty()) { 116 val errorMsgBuilder = 117 ExceptionMessageBuilder() 118 .forSubject(this) 119 .forInvalidElement( 120 componentMatcher?.toWindowIdentifier() ?: "<any>", 121 expectElementExists = true, 122 ) 123 throw InvalidElementException(errorMsgBuilder) 124 } 125 126 val visibleWindows = selectedWindows.filter { it.isVisible } 127 val visibleRegions = visibleWindows.map { it.windowState.frameRegion } 128 return RegionSubject(visibleRegions, timestamp, reader) 129 } 130 131 /** {@inheritDoc} */ 132 override fun containsAboveAppWindow( 133 componentMatcher: IComponentMatcher 134 ): WindowManagerStateSubject = apply { 135 if (!wmState.contains(componentMatcher)) { 136 throw createElementNotFoundException(componentMatcher) 137 } 138 if (!wmState.isAboveAppWindow(componentMatcher)) { 139 throw createElementNotFoundException(componentMatcher) 140 } 141 } 142 143 /** {@inheritDoc} */ 144 override fun containsBelowAppWindow( 145 componentMatcher: IComponentMatcher 146 ): WindowManagerStateSubject = apply { 147 if (!wmState.contains(componentMatcher)) { 148 throw createElementNotFoundException(componentMatcher) 149 } 150 if (!wmState.isBelowAppWindow(componentMatcher)) { 151 throw createElementNotFoundException(componentMatcher) 152 } 153 } 154 155 /** {@inheritDoc} */ 156 override fun isAboveWindow( 157 aboveWindowComponentMatcher: IComponentMatcher, 158 belowWindowComponentMatcher: IComponentMatcher, 159 ): WindowManagerStateSubject = apply { 160 contains(aboveWindowComponentMatcher) 161 contains(belowWindowComponentMatcher) 162 163 val aboveWindow = 164 wmState.windowStates.first { aboveWindowComponentMatcher.windowMatchesAnyOf(it) } 165 val belowWindow = 166 wmState.windowStates.first { belowWindowComponentMatcher.windowMatchesAnyOf(it) } 167 168 val errorMsgBuilder = 169 ExceptionMessageBuilder() 170 .forSubject(this) 171 .addExtraDescription( 172 Fact("Above window filter", aboveWindowComponentMatcher.toWindowIdentifier()) 173 ) 174 .addExtraDescription( 175 Fact("Below window filter", belowWindowComponentMatcher.toWindowIdentifier()) 176 ) 177 178 if (aboveWindow == belowWindow) { 179 errorMsgBuilder 180 .setMessage("Above and below windows should be different") 181 .setActual(aboveWindow.title) 182 throw SubjectAssertionError(errorMsgBuilder) 183 } 184 185 // windows are ordered by z-order, from top to bottom 186 val aboveZ = 187 wmState.windowStates.indexOfFirst { aboveWindowComponentMatcher.windowMatchesAnyOf(it) } 188 val belowZ = 189 wmState.windowStates.indexOfFirst { belowWindowComponentMatcher.windowMatchesAnyOf(it) } 190 if (aboveZ >= belowZ) { 191 errorMsgBuilder 192 .setMessage("${aboveWindow.title} should be above ${belowWindow.title}") 193 .setActual("${belowWindow.title} is above") 194 .setExpected("${aboveWindow.title} is below") 195 throw SubjectAssertionError(errorMsgBuilder) 196 } 197 } 198 199 /** {@inheritDoc} */ 200 override fun containsNonAppWindow( 201 componentMatcher: IComponentMatcher 202 ): WindowManagerStateSubject = apply { 203 if (!wmState.contains(componentMatcher)) { 204 throw createElementNotFoundException(componentMatcher) 205 } 206 if (!wmState.isNonAppWindow(componentMatcher)) { 207 throw createElementNotFoundException(componentMatcher) 208 } 209 } 210 211 /** {@inheritDoc} */ 212 override fun isAppWindowOnTop(componentMatcher: IComponentMatcher): WindowManagerStateSubject = 213 apply { 214 if (wmState.visibleAppWindows.isEmpty()) { 215 val errorMsgBuilder = 216 ExceptionMessageBuilder() 217 .forSubject(this) 218 .forInvalidElement( 219 componentMatcher.toWindowIdentifier(), 220 expectElementExists = true, 221 ) 222 .addExtraDescription("Type", "App window") 223 throw InvalidElementException(errorMsgBuilder) 224 } 225 226 val topVisibleAppWindow = wmState.topVisibleAppWindow 227 val topWindowMatches = 228 topVisibleAppWindow != null && 229 componentMatcher.windowMatchesAnyOf(topVisibleAppWindow) 230 231 if (!topWindowMatches) { 232 isNotEmpty() 233 234 val errorMsgBuilder = 235 ExceptionMessageBuilder() 236 .forSubject(this) 237 .forInvalidProperty("Top visible app window") 238 .setActual(topVisibleAppWindow?.name) 239 .setExpected(componentMatcher.toWindowIdentifier()) 240 throw InvalidPropertyException(errorMsgBuilder) 241 } 242 } 243 244 /** {@inheritDoc} */ 245 override fun isAppWindowNotOnTop( 246 componentMatcher: IComponentMatcher 247 ): WindowManagerStateSubject = apply { 248 val topVisibleAppWindow = wmState.topVisibleAppWindow 249 if ( 250 topVisibleAppWindow != null && componentMatcher.windowMatchesAnyOf(topVisibleAppWindow) 251 ) { 252 val topWindow = subjects.first { it.windowState == topVisibleAppWindow } 253 val errorMsgBuilder = 254 ExceptionMessageBuilder() 255 .forSubject(this) 256 .forInvalidProperty("${topWindow.name} should not be on top") 257 .setActual(topWindow.name) 258 .setExpected(componentMatcher.toWindowIdentifier()) 259 .addExtraDescription("Type", "App window") 260 .addExtraDescription("Filter", componentMatcher.toWindowIdentifier()) 261 throw InvalidPropertyException(errorMsgBuilder) 262 } 263 } 264 265 /** {@inheritDoc} */ 266 override fun doNotOverlap( 267 vararg componentMatcher: IComponentMatcher 268 ): WindowManagerStateSubject = apply { 269 val componentNames = componentMatcher.joinToString(", ") { it.toWindowIdentifier() } 270 if (componentMatcher.size == 1) { 271 throw IllegalArgumentException( 272 "Must give more than one window to check! (Given $componentNames)" 273 ) 274 } 275 276 componentMatcher.forEach { contains(it) } 277 val foundWindows = 278 componentMatcher 279 .toSet() 280 .associateWith { act -> 281 wmState.windowStates.firstOrNull { act.windowMatchesAnyOf(it) } 282 } 283 // keep entries only for windows that we actually found by removing nulls 284 .filterValues { it != null } 285 val foundWindowsRegions = foundWindows.mapValues { (_, v) -> v?.frameRegion ?: Region() } 286 287 val regions = foundWindowsRegions.entries.toList() 288 for (i in regions.indices) { 289 val (ourTitle, ourRegion) = regions[i] 290 for (j in i + 1 until regions.size) { 291 val (otherTitle, otherRegion) = regions[j] 292 val overlapRegion = Region(ourRegion) 293 if (overlapRegion.op(otherRegion, Region.Op.INTERSECT)) { 294 val errorMsgBuilder = 295 ExceptionMessageBuilder() 296 .forSubject(this) 297 .setMessage("$componentNames should not overlap") 298 .setActual("$ourTitle overlaps with $otherTitle") 299 .addExtraDescription("$ourTitle region", ourRegion) 300 .addExtraDescription("$otherTitle region", otherRegion) 301 .addExtraDescription("Overlap region", overlapRegion) 302 throw SubjectAssertionError(errorMsgBuilder) 303 } 304 } 305 } 306 } 307 308 /** {@inheritDoc} */ 309 override fun containsAppWindow(componentMatcher: IComponentMatcher): WindowManagerStateSubject = 310 apply { 311 // Check existence of activity 312 val activity = wmState.getActivitiesForWindow(componentMatcher).firstOrNull() 313 314 if (activity == null) { 315 val errorMsgBuilder = 316 ExceptionMessageBuilder() 317 .forSubject(this) 318 .forInvalidElement( 319 componentMatcher.toActivityIdentifier(), 320 expectElementExists = true, 321 ) 322 throw InvalidElementException(errorMsgBuilder) 323 } 324 // Check existence of window. 325 contains(componentMatcher) 326 } 327 328 /** {@inheritDoc} */ 329 override fun hasRotation(rotation: Rotation, displayId: Int): WindowManagerStateSubject = 330 apply { 331 check { "rotation" }.that(wmState.getRotation(displayId)).isEqual(rotation) 332 } 333 334 /** {@inheritDoc} */ 335 override fun contains(componentMatcher: IComponentMatcher): WindowManagerStateSubject = apply { 336 contains(subjects, componentMatcher) 337 } 338 339 /** {@inheritDoc} */ 340 override fun notContainsAppWindow( 341 componentMatcher: IComponentMatcher 342 ): WindowManagerStateSubject = apply { 343 // system components (e.g., NavBar, StatusBar, PipOverlay) don't have a package name 344 // nor an activity, ignore them 345 if (wmState.containsActivity(componentMatcher)) { 346 val errorMsgBuilder = 347 ExceptionMessageBuilder() 348 .forSubject(this) 349 .forInvalidElement( 350 componentMatcher.toActivityIdentifier(), 351 expectElementExists = false, 352 ) 353 throw InvalidElementException(errorMsgBuilder) 354 } 355 notContains(componentMatcher) 356 } 357 358 /** {@inheritDoc} */ 359 override fun notContains(componentMatcher: IComponentMatcher): WindowManagerStateSubject = 360 apply { 361 if (wmState.containsWindow(componentMatcher)) { 362 val errorMsgBuilder = 363 ExceptionMessageBuilder() 364 .forSubject(this) 365 .forInvalidElement( 366 componentMatcher.toWindowIdentifier(), 367 expectElementExists = false, 368 ) 369 throw InvalidElementException(errorMsgBuilder) 370 } 371 } 372 373 /** {@inheritDoc} */ 374 override fun isRecentsActivityVisible(): WindowManagerStateSubject = apply { 375 if (wmState.isHomeRecentsComponent) { 376 isHomeActivityVisible() 377 } else { 378 if (!wmState.isRecentsActivityVisible) { 379 val errorMsgBuilder = 380 ExceptionMessageBuilder() 381 .forSubject(this) 382 .forIncorrectVisibility("Recents activity", expectElementVisible = true) 383 .setActual(wmState.isRecentsActivityVisible) 384 throw IncorrectVisibilityException(errorMsgBuilder) 385 } 386 } 387 } 388 389 /** {@inheritDoc} */ 390 override fun isRecentsActivityInvisible(): WindowManagerStateSubject = apply { 391 if (wmState.isHomeRecentsComponent) { 392 isHomeActivityInvisible() 393 } else { 394 if (wmState.isRecentsActivityVisible) { 395 val errorMsgBuilder = 396 ExceptionMessageBuilder() 397 .forSubject(this) 398 .forIncorrectVisibility("Recents activity", expectElementVisible = false) 399 .setActual(wmState.isRecentsActivityVisible) 400 throw IncorrectVisibilityException(errorMsgBuilder) 401 } 402 } 403 } 404 405 /** {@inheritDoc} */ 406 override fun isValid(): WindowManagerStateSubject = apply { 407 check { "Stacks count" }.that(wmState.stackCount).isGreater(0) 408 // TODO: Update when keyguard will be shown on multiple displays 409 if (!wmState.keyguardControllerState.isKeyguardShowing) { 410 check { "Resumed activity" }.that(wmState.resumedActivitiesCount).isGreater(0) 411 } 412 check { "No focused activity" }.that(wmState.focusedActivity).isNotEqual(null) 413 wmState.rootTasks.forEach { aStack -> 414 val stackId = aStack.rootTaskId 415 aStack.tasks.forEach { aTask -> 416 check { "Root task Id for stack $aTask" }.that(stackId).isEqual(aTask.rootTaskId) 417 } 418 } 419 check { "Front window" }.that(wmState.frontWindow).isNotNull() 420 check { "Focused window" }.that(wmState.focusedWindow).isNotNull() 421 check { "Focused app" }.that(wmState.focusedApp.isNotEmpty()).isEqual(true) 422 } 423 424 /** {@inheritDoc} */ 425 override fun isNonAppWindowVisible( 426 componentMatcher: IComponentMatcher 427 ): WindowManagerStateSubject = apply { 428 if (!wmState.contains(componentMatcher)) { 429 throw createElementNotFoundException(componentMatcher) 430 } 431 if (!wmState.isNonAppWindow(componentMatcher)) { 432 throw createElementNotFoundException(componentMatcher) 433 } 434 if (!wmState.isVisible(componentMatcher)) { 435 throw createIncorrectVisibilityException(componentMatcher, expectElementVisible = true) 436 } 437 } 438 439 /** {@inheritDoc} */ 440 override fun isAppWindowVisible( 441 componentMatcher: IComponentMatcher 442 ): WindowManagerStateSubject = apply { 443 if (!wmState.contains(componentMatcher)) { 444 throw createElementNotFoundException(componentMatcher) 445 } 446 if (!wmState.isAppWindow(componentMatcher)) { 447 throw createElementNotFoundException(componentMatcher) 448 } 449 if (!wmState.isVisible(componentMatcher)) { 450 throw createIncorrectVisibilityException(componentMatcher, expectElementVisible = true) 451 } 452 } 453 454 /** {@inheritDoc} */ 455 override fun hasNoVisibleAppWindow(): WindowManagerStateSubject = apply { 456 check { "Visible app windows" } 457 .that(visibleAppWindows.joinToString(", ") { it.name }) 458 .isEqual("") 459 } 460 461 /** {@inheritDoc} */ 462 override fun isKeyguardShowing(): WindowManagerStateSubject = apply { 463 check { "Keyguard or AOD showing" } 464 .that(wmState.isKeyguardShowing || wmState.isAodShowing) 465 .isEqual(true) 466 } 467 468 /** {@inheritDoc} */ 469 override fun isAppWindowInvisible( 470 componentMatcher: IComponentMatcher 471 ): WindowManagerStateSubject = apply { checkWindowIsInvisible(appWindows, componentMatcher) } 472 473 /** {@inheritDoc} */ 474 override fun isNonAppWindowInvisible( 475 componentMatcher: IComponentMatcher 476 ): WindowManagerStateSubject = apply { checkWindowIsInvisible(nonAppWindows, componentMatcher) } 477 478 private fun checkWindowIsInvisible( 479 subjectList: List<WindowStateSubject>, 480 componentMatcher: IComponentMatcher, 481 ) { 482 val foundWindows = 483 subjectList.filter { componentMatcher.windowMatchesAnyOf(it.windowState) } 484 485 val visibleWindows = 486 wmState.visibleWindows.filter { visibleWindow -> 487 foundWindows.any { it.windowState == visibleWindow } 488 } 489 490 if (visibleWindows.isNotEmpty()) { 491 val errorMsgBuilder = 492 ExceptionMessageBuilder() 493 .forSubject(this) 494 .forIncorrectVisibility( 495 componentMatcher.toWindowIdentifier(), 496 expectElementVisible = false, 497 ) 498 .setActual(visibleWindows.map { Fact("Is visible", it.name) }) 499 throw IncorrectVisibilityException(errorMsgBuilder) 500 } 501 } 502 503 private fun contains( 504 subjectList: List<WindowStateSubject>, 505 componentMatcher: IComponentMatcher, 506 ) { 507 if (!componentMatcher.windowMatchesAnyOf(subjectList.map { it.windowState })) { 508 val errorMsgBuilder = 509 ExceptionMessageBuilder() 510 .forSubject(this) 511 .forInvalidElement( 512 componentMatcher.toWindowIdentifier(), 513 expectElementExists = true, 514 ) 515 throw InvalidElementException(errorMsgBuilder) 516 } 517 } 518 519 private fun createIncorrectVisibilityException( 520 componentMatcher: IComponentMatcher, 521 expectElementVisible: Boolean, 522 ) = 523 IncorrectVisibilityException( 524 ExceptionMessageBuilder() 525 .forSubject(this) 526 .forIncorrectVisibility(componentMatcher.toWindowIdentifier(), expectElementVisible) 527 ) 528 529 private fun createElementNotFoundException(componentMatcher: IComponentMatcher) = 530 InvalidElementException( 531 ExceptionMessageBuilder() 532 .forSubject(this) 533 .forInvalidElement( 534 componentMatcher.toWindowIdentifier(), 535 expectElementExists = true, 536 ) 537 ) 538 539 /** {@inheritDoc} */ 540 override fun isHomeActivityVisible(): WindowManagerStateSubject = apply { 541 if (!wmState.isHomeActivityVisible) { 542 val errorMsgBuilder = 543 ExceptionMessageBuilder() 544 .forSubject(this) 545 .forIncorrectVisibility("Home activity", expectElementVisible = true) 546 throw IncorrectVisibilityException(errorMsgBuilder) 547 } 548 } 549 550 /** {@inheritDoc} */ 551 override fun isHomeActivityInvisible(): WindowManagerStateSubject = apply { 552 val homeIsVisible = wmState.homeActivity?.isVisible ?: false 553 if (homeIsVisible) { 554 val errorMsgBuilder = 555 ExceptionMessageBuilder() 556 .forSubject(this) 557 .forIncorrectVisibility("Home activity", expectElementVisible = false) 558 throw IncorrectVisibilityException(errorMsgBuilder) 559 } 560 } 561 562 /** {@inheritDoc} */ 563 override fun isFocusedApp(app: String): WindowManagerStateSubject = apply { 564 check { "Window is focused app $app" }.that(wmState.focusedApp).isEqual(app) 565 } 566 567 /** {@inheritDoc} */ 568 override fun isNotFocusedApp(app: String): WindowManagerStateSubject = apply { 569 check { "Window is not focused app $app" }.that(wmState.focusedApp).isNotEqual(app) 570 } 571 572 /** {@inheritDoc} */ 573 override fun isPinned(componentMatcher: IComponentMatcher): WindowManagerStateSubject = apply { 574 contains(componentMatcher) 575 check { "Window is pinned ${componentMatcher.toWindowIdentifier()}" } 576 .that(wmState.isInPipMode(componentMatcher)) 577 .isEqual(true) 578 } 579 580 /** {@inheritDoc} */ 581 override fun isNotPinned(componentMatcher: IComponentMatcher): WindowManagerStateSubject = 582 apply { 583 contains(componentMatcher) 584 check { "Window is pinned ${componentMatcher.toWindowIdentifier()}" } 585 .that(wmState.isInPipMode(componentMatcher)) 586 .isEqual(false) 587 } 588 589 /** {@inheritDoc} */ 590 override fun isAppSnapshotStartingWindowVisibleFor( 591 componentMatcher: IComponentMatcher 592 ): WindowManagerStateSubject = apply { 593 val activity = wmState.getActivitiesForWindow(componentMatcher).firstOrNull() 594 595 if (activity == null) { 596 val errorMsgBuilder = 597 ExceptionMessageBuilder() 598 .forSubject(this) 599 .forInvalidElement( 600 componentMatcher.toActivityIdentifier(), 601 expectElementExists = true, 602 ) 603 throw InvalidElementException(errorMsgBuilder) 604 } 605 606 // Check existence and visibility of SnapshotStartingWindow 607 val snapshotStartingWindow = 608 activity.getWindows(ComponentNameMatcher.SNAPSHOT).firstOrNull() 609 610 if (snapshotStartingWindow == null) { 611 val errorMsgBuilder = 612 ExceptionMessageBuilder() 613 .forSubject(this) 614 .forInvalidElement( 615 ComponentNameMatcher.SNAPSHOT.toWindowIdentifier(), 616 expectElementExists = true, 617 ) 618 throw InvalidElementException(errorMsgBuilder) 619 } 620 621 if (!activity.isVisible) { 622 val errorMsgBuilder = 623 ExceptionMessageBuilder() 624 .forSubject(this) 625 .forIncorrectVisibility( 626 componentMatcher.toActivityIdentifier(), 627 expectElementVisible = true, 628 ) 629 throw IncorrectVisibilityException(errorMsgBuilder) 630 } 631 632 if (!snapshotStartingWindow.isVisible) { 633 val errorMsgBuilder = 634 ExceptionMessageBuilder() 635 .forSubject(this) 636 .forIncorrectVisibility( 637 ComponentNameMatcher.SNAPSHOT.toWindowIdentifier(), 638 expectElementVisible = true, 639 ) 640 throw IncorrectVisibilityException(errorMsgBuilder) 641 } 642 } 643 644 /** {@inheritDoc} */ 645 override fun isAboveAppWindowVisible( 646 componentMatcher: IComponentMatcher 647 ): WindowManagerStateSubject = 648 containsAboveAppWindow(componentMatcher).isNonAppWindowVisible(componentMatcher) 649 650 /** {@inheritDoc} */ 651 override fun isAboveAppWindowInvisible( 652 componentMatcher: IComponentMatcher 653 ): WindowManagerStateSubject = 654 containsAboveAppWindow(componentMatcher).isNonAppWindowInvisible(componentMatcher) 655 656 /** {@inheritDoc} */ 657 override fun isBelowAppWindowVisible( 658 componentMatcher: IComponentMatcher 659 ): WindowManagerStateSubject = 660 containsBelowAppWindow(componentMatcher).isNonAppWindowVisible(componentMatcher) 661 662 /** {@inheritDoc} */ 663 override fun isBelowAppWindowInvisible( 664 componentMatcher: IComponentMatcher 665 ): WindowManagerStateSubject = 666 containsBelowAppWindow(componentMatcher).isNonAppWindowInvisible(componentMatcher) 667 668 /** {@inheritDoc} */ 669 override fun containsAtLeastOneDisplay(): WindowManagerStateSubject = apply { 670 check { "Displays" }.that(wmState.displays.size).isGreater(0) 671 } 672 673 /** Obtains the first subject with [WindowState.title] containing [name]. */ 674 fun windowState(name: String): WindowStateSubject? = windowState { it.name.contains(name) } 675 676 /** 677 * Obtains the first subject matching [predicate]. 678 * 679 * @param predicate to search for a subject 680 */ 681 fun windowState(predicate: Predicate<WindowState>): WindowStateSubject? = 682 subjects.firstOrNull { predicate.test(it.windowState) } 683 684 override fun toString(): String { 685 return "WindowManagerStateSubject($wmState)" 686 } 687 } 688