• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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