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