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