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.io.Reader 30 import android.tools.traces.component.ComponentNameMatcher 31 import android.tools.traces.component.IComponentMatcher 32 import android.tools.traces.wm.WindowManagerState 33 import android.tools.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 } 125 return RegionSubject(visibleRegions, timestamp, reader) 126 } 127 128 /** {@inheritDoc} */ 129 override fun containsAboveAppWindow( 130 componentMatcher: IComponentMatcher 131 ): WindowManagerStateSubject = apply { 132 if (!wmState.contains(componentMatcher)) { 133 throw createElementNotFoundException(componentMatcher) 134 } 135 if (!wmState.isAboveAppWindow(componentMatcher)) { 136 throw createElementNotFoundException(componentMatcher) 137 } 138 } 139 140 /** {@inheritDoc} */ 141 override fun containsBelowAppWindow( 142 componentMatcher: IComponentMatcher 143 ): WindowManagerStateSubject = apply { 144 if (!wmState.contains(componentMatcher)) { 145 throw createElementNotFoundException(componentMatcher) 146 } 147 if (!wmState.isBelowAppWindow(componentMatcher)) { 148 throw createElementNotFoundException(componentMatcher) 149 } 150 } 151 152 /** {@inheritDoc} */ 153 override fun isAboveWindow( 154 aboveWindowComponentMatcher: IComponentMatcher, 155 belowWindowComponentMatcher: IComponentMatcher 156 ): WindowManagerStateSubject = apply { 157 contains(aboveWindowComponentMatcher) 158 contains(belowWindowComponentMatcher) 159 160 val aboveWindow = 161 wmState.windowStates.first { aboveWindowComponentMatcher.windowMatchesAnyOf(it) } 162 val belowWindow = 163 wmState.windowStates.first { belowWindowComponentMatcher.windowMatchesAnyOf(it) } 164 165 val errorMsgBuilder = 166 ExceptionMessageBuilder() 167 .forSubject(this) 168 .addExtraDescription( 169 Fact("Above window filter", aboveWindowComponentMatcher.toWindowIdentifier()) 170 ) 171 .addExtraDescription( 172 Fact("Below window filter", belowWindowComponentMatcher.toWindowIdentifier()) 173 ) 174 175 if (aboveWindow == belowWindow) { 176 errorMsgBuilder 177 .setMessage("Above and below windows should be different") 178 .setActual(aboveWindow.title) 179 throw SubjectAssertionError(errorMsgBuilder) 180 } 181 182 // windows are ordered by z-order, from top to bottom 183 val aboveZ = 184 wmState.windowStates.indexOfFirst { aboveWindowComponentMatcher.windowMatchesAnyOf(it) } 185 val belowZ = 186 wmState.windowStates.indexOfFirst { belowWindowComponentMatcher.windowMatchesAnyOf(it) } 187 if (aboveZ >= belowZ) { 188 errorMsgBuilder 189 .setMessage("${aboveWindow.title} should be above ${belowWindow.title}") 190 .setActual("${belowWindow.title} is above") 191 .setExpected("${aboveWindow.title} is below") 192 throw SubjectAssertionError(errorMsgBuilder) 193 } 194 } 195 196 /** {@inheritDoc} */ 197 override fun containsNonAppWindow( 198 componentMatcher: IComponentMatcher 199 ): WindowManagerStateSubject = apply { 200 if (!wmState.contains(componentMatcher)) { 201 throw createElementNotFoundException(componentMatcher) 202 } 203 if (!wmState.isNonAppWindow(componentMatcher)) { 204 throw createElementNotFoundException(componentMatcher) 205 } 206 } 207 208 /** {@inheritDoc} */ 209 override fun isAppWindowOnTop(componentMatcher: IComponentMatcher): WindowManagerStateSubject = 210 apply { 211 if (wmState.visibleAppWindows.isEmpty()) { 212 val errorMsgBuilder = 213 ExceptionMessageBuilder() 214 .forSubject(this) 215 .forInvalidElement( 216 componentMatcher.toWindowIdentifier(), 217 expectElementExists = true 218 ) 219 .addExtraDescription("Type", "App window") 220 throw InvalidElementException(errorMsgBuilder) 221 } 222 223 val topVisibleAppWindow = wmState.topVisibleAppWindow 224 val topWindowMatches = 225 topVisibleAppWindow != null && 226 componentMatcher.windowMatchesAnyOf(topVisibleAppWindow) 227 228 if (!topWindowMatches) { 229 isNotEmpty() 230 231 val errorMsgBuilder = 232 ExceptionMessageBuilder() 233 .forSubject(this) 234 .forInvalidProperty("Top visible app window") 235 .setActual(topVisibleAppWindow?.name) 236 .setExpected(componentMatcher.toWindowIdentifier()) 237 throw InvalidPropertyException(errorMsgBuilder) 238 } 239 } 240 241 /** {@inheritDoc} */ 242 override fun isAppWindowNotOnTop( 243 componentMatcher: IComponentMatcher 244 ): WindowManagerStateSubject = apply { 245 val topVisibleAppWindow = wmState.topVisibleAppWindow 246 if ( 247 topVisibleAppWindow != null && componentMatcher.windowMatchesAnyOf(topVisibleAppWindow) 248 ) { 249 val topWindow = subjects.first { it.windowState == topVisibleAppWindow } 250 val errorMsgBuilder = 251 ExceptionMessageBuilder() 252 .forSubject(this) 253 .forInvalidProperty("${topWindow.name} should not be on top") 254 .setActual(topWindow.name) 255 .setExpected(componentMatcher.toWindowIdentifier()) 256 .addExtraDescription("Type", "App window") 257 .addExtraDescription("Filter", componentMatcher.toWindowIdentifier()) 258 throw InvalidPropertyException(errorMsgBuilder) 259 } 260 } 261 262 /** {@inheritDoc} */ 263 override fun doNotOverlap( 264 vararg componentMatcher: IComponentMatcher 265 ): WindowManagerStateSubject = apply { 266 val componentNames = componentMatcher.joinToString(", ") { it.toWindowIdentifier() } 267 if (componentMatcher.size == 1) { 268 throw IllegalArgumentException( 269 "Must give more than one window to check! (Given $componentNames)" 270 ) 271 } 272 273 componentMatcher.forEach { contains(it) } 274 val foundWindows = 275 componentMatcher 276 .toSet() 277 .associateWith { act -> 278 wmState.windowStates.firstOrNull { act.windowMatchesAnyOf(it) } 279 } 280 // keep entries only for windows that we actually found by removing nulls 281 .filterValues { it != null } 282 val foundWindowsRegions = foundWindows.mapValues { (_, v) -> v?.frameRegion ?: Region() } 283 284 val regions = foundWindowsRegions.entries.toList() 285 for (i in regions.indices) { 286 val (ourTitle, ourRegion) = regions[i] 287 for (j in i + 1 until regions.size) { 288 val (otherTitle, otherRegion) = regions[j] 289 val overlapRegion = Region(ourRegion) 290 if (overlapRegion.op(otherRegion, Region.Op.INTERSECT)) { 291 val errorMsgBuilder = 292 ExceptionMessageBuilder() 293 .forSubject(this) 294 .setMessage("$componentNames should not overlap") 295 .setActual("$ourTitle overlaps with $otherTitle") 296 .addExtraDescription("$ourTitle region", ourRegion) 297 .addExtraDescription("$otherTitle region", otherRegion) 298 .addExtraDescription("Overlap region", overlapRegion) 299 throw SubjectAssertionError(errorMsgBuilder) 300 } 301 } 302 } 303 } 304 305 /** {@inheritDoc} */ 306 override fun containsAppWindow(componentMatcher: IComponentMatcher): WindowManagerStateSubject = 307 apply { 308 // Check existence of activity 309 val activity = wmState.getActivitiesForWindow(componentMatcher).firstOrNull() 310 311 if (activity == null) { 312 val errorMsgBuilder = 313 ExceptionMessageBuilder() 314 .forSubject(this) 315 .forInvalidElement( 316 componentMatcher.toActivityIdentifier(), 317 expectElementExists = true 318 ) 319 throw InvalidElementException(errorMsgBuilder) 320 } 321 // Check existence of window. 322 contains(componentMatcher) 323 } 324 325 /** {@inheritDoc} */ 326 override fun hasRotation(rotation: Rotation, displayId: Int): WindowManagerStateSubject = 327 apply { 328 check { "rotation" }.that(wmState.getRotation(displayId)).isEqual(rotation) 329 } 330 331 /** {@inheritDoc} */ 332 override fun contains(componentMatcher: IComponentMatcher): WindowManagerStateSubject = apply { 333 contains(subjects, componentMatcher) 334 } 335 336 /** {@inheritDoc} */ 337 override fun notContainsAppWindow( 338 componentMatcher: IComponentMatcher 339 ): WindowManagerStateSubject = apply { 340 // system components (e.g., NavBar, StatusBar, PipOverlay) don't have a package name 341 // nor an activity, ignore them 342 if (wmState.containsActivity(componentMatcher)) { 343 val errorMsgBuilder = 344 ExceptionMessageBuilder() 345 .forSubject(this) 346 .forInvalidElement( 347 componentMatcher.toActivityIdentifier(), 348 expectElementExists = false 349 ) 350 throw InvalidElementException(errorMsgBuilder) 351 } 352 notContains(componentMatcher) 353 } 354 355 /** {@inheritDoc} */ 356 override fun notContains(componentMatcher: IComponentMatcher): WindowManagerStateSubject = 357 apply { 358 if (wmState.containsWindow(componentMatcher)) { 359 val errorMsgBuilder = 360 ExceptionMessageBuilder() 361 .forSubject(this) 362 .forInvalidElement( 363 componentMatcher.toWindowIdentifier(), 364 expectElementExists = false 365 ) 366 throw InvalidElementException(errorMsgBuilder) 367 } 368 } 369 370 /** {@inheritDoc} */ 371 override fun isRecentsActivityVisible(): WindowManagerStateSubject = apply { 372 if (wmState.isHomeRecentsComponent) { 373 isHomeActivityVisible() 374 } else { 375 if (!wmState.isRecentsActivityVisible) { 376 val errorMsgBuilder = 377 ExceptionMessageBuilder() 378 .forSubject(this) 379 .forIncorrectVisibility("Recents activity", expectElementVisible = true) 380 .setActual(wmState.isRecentsActivityVisible) 381 throw IncorrectVisibilityException(errorMsgBuilder) 382 } 383 } 384 } 385 386 /** {@inheritDoc} */ 387 override fun isRecentsActivityInvisible(): WindowManagerStateSubject = apply { 388 if (wmState.isHomeRecentsComponent) { 389 isHomeActivityInvisible() 390 } else { 391 if (wmState.isRecentsActivityVisible) { 392 val errorMsgBuilder = 393 ExceptionMessageBuilder() 394 .forSubject(this) 395 .forIncorrectVisibility("Recents activity", expectElementVisible = false) 396 .setActual(wmState.isRecentsActivityVisible) 397 throw IncorrectVisibilityException(errorMsgBuilder) 398 } 399 } 400 } 401 402 /** {@inheritDoc} */ 403 override fun isValid(): WindowManagerStateSubject = apply { 404 check { "Stacks count" }.that(wmState.stackCount).isGreater(0) 405 // TODO: Update when keyguard will be shown on multiple displays 406 if (!wmState.keyguardControllerState.isKeyguardShowing) { 407 check { "Resumed activity" }.that(wmState.resumedActivitiesCount).isGreater(0) 408 } 409 check { "No focused activity" }.that(wmState.focusedActivity).isNotEqual(null) 410 wmState.rootTasks.forEach { aStack -> 411 val stackId = aStack.rootTaskId 412 aStack.tasks.forEach { aTask -> 413 check { "Root task Id for stack $aTask" }.that(stackId).isEqual(aTask.rootTaskId) 414 } 415 } 416 check { "Front window" }.that(wmState.frontWindow).isNotNull() 417 check { "Focused window" }.that(wmState.focusedWindow).isNotNull() 418 check { "Focused app" }.that(wmState.focusedApp.isNotEmpty()).isEqual(true) 419 } 420 421 /** {@inheritDoc} */ 422 override fun isNonAppWindowVisible( 423 componentMatcher: IComponentMatcher 424 ): WindowManagerStateSubject = apply { 425 if (!wmState.contains(componentMatcher)) { 426 throw createElementNotFoundException(componentMatcher) 427 } 428 if (!wmState.isNonAppWindow(componentMatcher)) { 429 throw createElementNotFoundException(componentMatcher) 430 } 431 if (!wmState.isVisible(componentMatcher)) { 432 throw createIncorrectVisibilityException(componentMatcher, expectElementVisible = true) 433 } 434 } 435 436 /** {@inheritDoc} */ 437 override fun isAppWindowVisible( 438 componentMatcher: IComponentMatcher 439 ): WindowManagerStateSubject = apply { 440 if (!wmState.contains(componentMatcher)) { 441 throw createElementNotFoundException(componentMatcher) 442 } 443 if (!wmState.isAppWindow(componentMatcher)) { 444 throw createElementNotFoundException(componentMatcher) 445 } 446 if (!wmState.isVisible(componentMatcher)) { 447 throw createIncorrectVisibilityException(componentMatcher, expectElementVisible = true) 448 } 449 } 450 451 /** {@inheritDoc} */ 452 override fun hasNoVisibleAppWindow(): WindowManagerStateSubject = apply { 453 check { "Visible app windows" } 454 .that(visibleAppWindows.joinToString(", ") { it.name }) 455 .isEqual("") 456 } 457 458 /** {@inheritDoc} */ 459 override fun isKeyguardShowing(): WindowManagerStateSubject = apply { 460 check { "Keyguard or AOD showing" } 461 .that( 462 wmState.isKeyguardShowing || wmState.isAodShowing, 463 ) 464 .isEqual(true) 465 } 466 467 /** {@inheritDoc} */ 468 override fun isAppWindowInvisible( 469 componentMatcher: IComponentMatcher 470 ): WindowManagerStateSubject = apply { checkWindowIsInvisible(appWindows, componentMatcher) } 471 472 /** {@inheritDoc} */ 473 override fun isNonAppWindowInvisible( 474 componentMatcher: IComponentMatcher 475 ): WindowManagerStateSubject = apply { checkWindowIsInvisible(nonAppWindows, componentMatcher) } 476 477 private fun checkWindowIsVisible( 478 subjectList: List<WindowStateSubject>, 479 componentMatcher: IComponentMatcher 480 ) { 481 // Check existence of window. 482 contains(subjectList, componentMatcher) 483 484 val foundWindows = 485 subjectList.filter { componentMatcher.windowMatchesAnyOf(it.windowState) } 486 487 val visibleWindows = 488 wmState.visibleWindows.filter { visibleWindow -> 489 foundWindows.any { it.windowState == visibleWindow } 490 } 491 492 if (visibleWindows.isEmpty()) { 493 val errorMsgBuilder = 494 ExceptionMessageBuilder() 495 .forSubject(this) 496 .forIncorrectVisibility( 497 componentMatcher.toWindowIdentifier(), 498 expectElementVisible = true 499 ) 500 .setActual(foundWindows.map { Fact("Is invisible", it.name) }) 501 throw IncorrectVisibilityException(errorMsgBuilder) 502 } 503 } 504 505 private fun checkWindowIsInvisible( 506 subjectList: List<WindowStateSubject>, 507 componentMatcher: IComponentMatcher 508 ) { 509 val foundWindows = 510 subjectList.filter { componentMatcher.windowMatchesAnyOf(it.windowState) } 511 512 val visibleWindows = 513 wmState.visibleWindows.filter { visibleWindow -> 514 foundWindows.any { it.windowState == visibleWindow } 515 } 516 517 if (visibleWindows.isNotEmpty()) { 518 val errorMsgBuilder = 519 ExceptionMessageBuilder() 520 .forSubject(this) 521 .forIncorrectVisibility( 522 componentMatcher.toWindowIdentifier(), 523 expectElementVisible = false 524 ) 525 .setActual(visibleWindows.map { Fact("Is visible", it.name) }) 526 throw IncorrectVisibilityException(errorMsgBuilder) 527 } 528 } 529 530 private fun contains( 531 subjectList: List<WindowStateSubject>, 532 componentMatcher: IComponentMatcher 533 ) { 534 if (!componentMatcher.windowMatchesAnyOf(subjectList.map { it.windowState })) { 535 val errorMsgBuilder = 536 ExceptionMessageBuilder() 537 .forSubject(this) 538 .forInvalidElement( 539 componentMatcher.toWindowIdentifier(), 540 expectElementExists = true 541 ) 542 throw InvalidElementException(errorMsgBuilder) 543 } 544 } 545 546 private fun createIncorrectVisibilityException( 547 componentMatcher: IComponentMatcher, 548 expectElementVisible: Boolean 549 ) = 550 IncorrectVisibilityException( 551 ExceptionMessageBuilder() 552 .forSubject(this) 553 .forIncorrectVisibility(componentMatcher.toWindowIdentifier(), expectElementVisible) 554 ) 555 556 private fun createElementNotFoundException(componentMatcher: IComponentMatcher) = 557 InvalidElementException( 558 ExceptionMessageBuilder() 559 .forSubject(this) 560 .forInvalidElement( 561 componentMatcher.toWindowIdentifier(), 562 expectElementExists = true 563 ) 564 ) 565 566 /** {@inheritDoc} */ 567 override fun isHomeActivityVisible(): WindowManagerStateSubject = apply { 568 if (!wmState.isHomeActivityVisible) { 569 val errorMsgBuilder = 570 ExceptionMessageBuilder() 571 .forSubject(this) 572 .forIncorrectVisibility("Home activity", expectElementVisible = true) 573 throw IncorrectVisibilityException(errorMsgBuilder) 574 } 575 } 576 577 /** {@inheritDoc} */ 578 override fun isHomeActivityInvisible(): WindowManagerStateSubject = apply { 579 val homeIsVisible = wmState.homeActivity?.isVisible ?: false 580 if (homeIsVisible) { 581 val errorMsgBuilder = 582 ExceptionMessageBuilder() 583 .forSubject(this) 584 .forIncorrectVisibility("Home activity", expectElementVisible = false) 585 throw IncorrectVisibilityException(errorMsgBuilder) 586 } 587 } 588 589 /** {@inheritDoc} */ 590 override fun isFocusedApp(app: String): WindowManagerStateSubject = apply { 591 check { "Window is focused app $app" }.that(wmState.focusedApp).isEqual(app) 592 } 593 594 /** {@inheritDoc} */ 595 override fun isNotFocusedApp(app: String): WindowManagerStateSubject = apply { 596 check { "Window is not focused app $app" }.that(wmState.focusedApp).isNotEqual(app) 597 } 598 599 /** {@inheritDoc} */ 600 override fun isPinned(componentMatcher: IComponentMatcher): WindowManagerStateSubject = apply { 601 contains(componentMatcher) 602 check { "Window is pinned ${componentMatcher.toWindowIdentifier()}" } 603 .that(wmState.isInPipMode(componentMatcher)) 604 .isEqual(true) 605 } 606 607 /** {@inheritDoc} */ 608 override fun isNotPinned(componentMatcher: IComponentMatcher): WindowManagerStateSubject = 609 apply { 610 contains(componentMatcher) 611 check { "Window is pinned ${componentMatcher.toWindowIdentifier()}" } 612 .that(wmState.isInPipMode(componentMatcher)) 613 .isEqual(false) 614 } 615 616 /** {@inheritDoc} */ 617 override fun isAppSnapshotStartingWindowVisibleFor( 618 componentMatcher: IComponentMatcher 619 ): WindowManagerStateSubject = apply { 620 val activity = wmState.getActivitiesForWindow(componentMatcher).firstOrNull() 621 622 if (activity == null) { 623 val errorMsgBuilder = 624 ExceptionMessageBuilder() 625 .forSubject(this) 626 .forInvalidElement( 627 componentMatcher.toActivityIdentifier(), 628 expectElementExists = true 629 ) 630 throw InvalidElementException(errorMsgBuilder) 631 } 632 633 // Check existence and visibility of SnapshotStartingWindow 634 val snapshotStartingWindow = 635 activity.getWindows(ComponentNameMatcher.SNAPSHOT).firstOrNull() 636 637 if (snapshotStartingWindow == null) { 638 val errorMsgBuilder = 639 ExceptionMessageBuilder() 640 .forSubject(this) 641 .forInvalidElement( 642 ComponentNameMatcher.SNAPSHOT.toWindowIdentifier(), 643 expectElementExists = true 644 ) 645 throw InvalidElementException(errorMsgBuilder) 646 } 647 648 if (!activity.isVisible) { 649 val errorMsgBuilder = 650 ExceptionMessageBuilder() 651 .forSubject(this) 652 .forIncorrectVisibility( 653 componentMatcher.toActivityIdentifier(), 654 expectElementVisible = true 655 ) 656 throw IncorrectVisibilityException(errorMsgBuilder) 657 } 658 659 if (!snapshotStartingWindow.isVisible) { 660 val errorMsgBuilder = 661 ExceptionMessageBuilder() 662 .forSubject(this) 663 .forIncorrectVisibility( 664 ComponentNameMatcher.SNAPSHOT.toWindowIdentifier(), 665 expectElementVisible = true 666 ) 667 throw IncorrectVisibilityException(errorMsgBuilder) 668 } 669 } 670 671 /** {@inheritDoc} */ 672 override fun isAboveAppWindowVisible( 673 componentMatcher: IComponentMatcher 674 ): WindowManagerStateSubject = 675 containsAboveAppWindow(componentMatcher).isNonAppWindowVisible(componentMatcher) 676 677 /** {@inheritDoc} */ 678 override fun isAboveAppWindowInvisible( 679 componentMatcher: IComponentMatcher 680 ): WindowManagerStateSubject = 681 containsAboveAppWindow(componentMatcher).isNonAppWindowInvisible(componentMatcher) 682 683 /** {@inheritDoc} */ 684 override fun isBelowAppWindowVisible( 685 componentMatcher: IComponentMatcher 686 ): WindowManagerStateSubject = 687 containsBelowAppWindow(componentMatcher).isNonAppWindowVisible(componentMatcher) 688 689 /** {@inheritDoc} */ 690 override fun isBelowAppWindowInvisible( 691 componentMatcher: IComponentMatcher 692 ): WindowManagerStateSubject = 693 containsBelowAppWindow(componentMatcher).isNonAppWindowInvisible(componentMatcher) 694 695 /** {@inheritDoc} */ 696 override fun containsAtLeastOneDisplay(): WindowManagerStateSubject = apply { 697 check { "Displays" }.that(wmState.displays.size).isGreater(0) 698 } 699 700 /** Obtains the first subject with [WindowState.title] containing [name]. */ 701 fun windowState(name: String): WindowStateSubject? = windowState { it.name.contains(name) } 702 703 /** 704 * Obtains the first subject matching [predicate]. 705 * 706 * @param predicate to search for a subject 707 */ 708 fun windowState(predicate: (WindowState) -> Boolean): WindowStateSubject? = 709 subjects.firstOrNull { predicate(it.windowState) } 710 711 override fun toString(): String { 712 return "WindowManagerStateSubject($wmState)" 713 } 714 } 715