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 mustExist: Boolean, 472 ): WindowManagerStateSubject = apply { 473 if (mustExist) { 474 containsAppWindow(componentMatcher) 475 } 476 checkWindowIsInvisible(appWindows, componentMatcher) 477 } 478 479 /** {@inheritDoc} */ 480 override fun isNonAppWindowInvisible( 481 componentMatcher: IComponentMatcher, 482 mustExist: Boolean, 483 ): WindowManagerStateSubject = apply { 484 if (mustExist) { 485 containsNonAppWindow(componentMatcher) 486 } 487 checkWindowIsInvisible(nonAppWindows, componentMatcher) 488 } 489 490 private fun checkWindowIsInvisible( 491 subjectList: List<WindowStateSubject>, 492 componentMatcher: IComponentMatcher, 493 ) { 494 val foundWindows = 495 subjectList.filter { componentMatcher.windowMatchesAnyOf(it.windowState) } 496 497 val visibleWindows = 498 wmState.visibleWindows.filter { visibleWindow -> 499 foundWindows.any { it.windowState == visibleWindow } 500 } 501 502 if (visibleWindows.isNotEmpty()) { 503 val errorMsgBuilder = 504 ExceptionMessageBuilder() 505 .forSubject(this) 506 .forIncorrectVisibility( 507 componentMatcher.toWindowIdentifier(), 508 expectElementVisible = false, 509 ) 510 .setActual(visibleWindows.map { Fact("Is visible", it.name) }) 511 throw IncorrectVisibilityException(errorMsgBuilder) 512 } 513 } 514 515 private fun contains( 516 subjectList: List<WindowStateSubject>, 517 componentMatcher: IComponentMatcher, 518 ) { 519 if (!componentMatcher.windowMatchesAnyOf(subjectList.map { it.windowState })) { 520 val errorMsgBuilder = 521 ExceptionMessageBuilder() 522 .forSubject(this) 523 .forInvalidElement( 524 componentMatcher.toWindowIdentifier(), 525 expectElementExists = true, 526 ) 527 throw InvalidElementException(errorMsgBuilder) 528 } 529 } 530 531 private fun createIncorrectVisibilityException( 532 componentMatcher: IComponentMatcher, 533 expectElementVisible: Boolean, 534 ) = 535 IncorrectVisibilityException( 536 ExceptionMessageBuilder() 537 .forSubject(this) 538 .forIncorrectVisibility(componentMatcher.toWindowIdentifier(), expectElementVisible) 539 ) 540 541 private fun createElementNotFoundException(componentMatcher: IComponentMatcher) = 542 InvalidElementException( 543 ExceptionMessageBuilder() 544 .forSubject(this) 545 .forInvalidElement( 546 componentMatcher.toWindowIdentifier(), 547 expectElementExists = true, 548 ) 549 ) 550 551 /** {@inheritDoc} */ 552 override fun isHomeActivityVisible(): WindowManagerStateSubject = apply { 553 if (!wmState.isHomeActivityVisible) { 554 val errorMsgBuilder = 555 ExceptionMessageBuilder() 556 .forSubject(this) 557 .forIncorrectVisibility("Home activity", expectElementVisible = true) 558 throw IncorrectVisibilityException(errorMsgBuilder) 559 } 560 } 561 562 /** {@inheritDoc} */ 563 override fun isHomeActivityInvisible(): WindowManagerStateSubject = apply { 564 val homeIsVisible = wmState.homeActivity?.isVisible ?: false 565 if (homeIsVisible) { 566 val errorMsgBuilder = 567 ExceptionMessageBuilder() 568 .forSubject(this) 569 .forIncorrectVisibility("Home activity", expectElementVisible = false) 570 throw IncorrectVisibilityException(errorMsgBuilder) 571 } 572 } 573 574 /** {@inheritDoc} */ 575 override fun isFocusedApp(app: String): WindowManagerStateSubject = apply { 576 check { "Window is focused app $app" }.that(wmState.focusedApp).isEqual(app) 577 } 578 579 /** {@inheritDoc} */ 580 override fun isNotFocusedApp(app: String): WindowManagerStateSubject = apply { 581 check { "Window is not focused app $app" }.that(wmState.focusedApp).isNotEqual(app) 582 } 583 584 /** {@inheritDoc} */ 585 override fun isPinned(componentMatcher: IComponentMatcher): WindowManagerStateSubject = apply { 586 contains(componentMatcher) 587 check { "Window is pinned ${componentMatcher.toWindowIdentifier()}" } 588 .that(wmState.isInPipMode(componentMatcher)) 589 .isEqual(true) 590 } 591 592 /** {@inheritDoc} */ 593 override fun isNotPinned(componentMatcher: IComponentMatcher): WindowManagerStateSubject = 594 apply { 595 contains(componentMatcher) 596 check { "Window is pinned ${componentMatcher.toWindowIdentifier()}" } 597 .that(wmState.isInPipMode(componentMatcher)) 598 .isEqual(false) 599 } 600 601 /** {@inheritDoc} */ 602 override fun isAppSnapshotStartingWindowVisibleFor( 603 componentMatcher: IComponentMatcher 604 ): WindowManagerStateSubject = apply { 605 val activity = wmState.getActivitiesForWindow(componentMatcher).firstOrNull() 606 607 if (activity == null) { 608 val errorMsgBuilder = 609 ExceptionMessageBuilder() 610 .forSubject(this) 611 .forInvalidElement( 612 componentMatcher.toActivityIdentifier(), 613 expectElementExists = true, 614 ) 615 throw InvalidElementException(errorMsgBuilder) 616 } 617 618 // Check existence and visibility of SnapshotStartingWindow 619 val snapshotStartingWindow = 620 activity.getWindows(ComponentNameMatcher.SNAPSHOT).firstOrNull() 621 622 if (snapshotStartingWindow == null) { 623 val errorMsgBuilder = 624 ExceptionMessageBuilder() 625 .forSubject(this) 626 .forInvalidElement( 627 ComponentNameMatcher.SNAPSHOT.toWindowIdentifier(), 628 expectElementExists = true, 629 ) 630 throw InvalidElementException(errorMsgBuilder) 631 } 632 633 if (!activity.isVisible) { 634 val errorMsgBuilder = 635 ExceptionMessageBuilder() 636 .forSubject(this) 637 .forIncorrectVisibility( 638 componentMatcher.toActivityIdentifier(), 639 expectElementVisible = true, 640 ) 641 throw IncorrectVisibilityException(errorMsgBuilder) 642 } 643 644 if (!snapshotStartingWindow.isVisible) { 645 val errorMsgBuilder = 646 ExceptionMessageBuilder() 647 .forSubject(this) 648 .forIncorrectVisibility( 649 ComponentNameMatcher.SNAPSHOT.toWindowIdentifier(), 650 expectElementVisible = true, 651 ) 652 throw IncorrectVisibilityException(errorMsgBuilder) 653 } 654 } 655 656 /** {@inheritDoc} */ 657 override fun isAboveAppWindowVisible( 658 componentMatcher: IComponentMatcher 659 ): WindowManagerStateSubject = 660 containsAboveAppWindow(componentMatcher).isNonAppWindowVisible(componentMatcher) 661 662 /** {@inheritDoc} */ 663 override fun isAboveAppWindowInvisible( 664 componentMatcher: IComponentMatcher 665 ): WindowManagerStateSubject = 666 containsAboveAppWindow(componentMatcher).isNonAppWindowInvisible(componentMatcher) 667 668 /** {@inheritDoc} */ 669 override fun isBelowAppWindowVisible( 670 componentMatcher: IComponentMatcher 671 ): WindowManagerStateSubject = 672 containsBelowAppWindow(componentMatcher).isNonAppWindowVisible(componentMatcher) 673 674 /** {@inheritDoc} */ 675 override fun isBelowAppWindowInvisible( 676 componentMatcher: IComponentMatcher 677 ): WindowManagerStateSubject = 678 containsBelowAppWindow(componentMatcher).isNonAppWindowInvisible(componentMatcher) 679 680 /** {@inheritDoc} */ 681 override fun containsAtLeastOneDisplay(): WindowManagerStateSubject = apply { 682 check { "Displays" }.that(wmState.displays.size).isGreater(0) 683 } 684 685 /** Obtains the first subject with [WindowState.title] containing [name]. */ 686 fun windowState(name: String): WindowStateSubject? = windowState { it.name.contains(name) } 687 688 /** 689 * Obtains the first subject matching [predicate]. 690 * 691 * @param predicate to search for a subject 692 */ 693 fun windowState(predicate: Predicate<WindowState>): WindowStateSubject? = 694 subjects.firstOrNull { predicate.test(it.windowState) } 695 696 override fun toString(): String { 697 return "WindowManagerStateSubject($wmState)" 698 } 699 } 700