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