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.tools.Rotation
20 import android.tools.flicker.subject.FlickerTraceSubject
21 import android.tools.flicker.subject.region.RegionTraceSubject
22 import android.tools.io.Reader
23 import android.tools.traces.component.ComponentNameMatcher
24 import android.tools.traces.component.IComponentMatcher
25 import android.tools.traces.region.RegionTrace
26 import android.tools.traces.wm.WindowManagerTrace
27 import android.tools.traces.wm.WindowState
28 
29 /**
30  * Subject for [WindowManagerTrace] objects, used to make assertions over behaviors that occur
31  * throughout a whole trace.
32  *
33  * To make assertions over a trace it is recommended to create a subject using
34  * [WindowManagerTraceSubject](myTrace).
35  *
36  * Example:
37  * ```
38  *    val trace = WindowManagerTraceParser().parse(myTraceFile)
39  *    val subject = WindowManagerTraceSubject(trace)
40  *        .contains("ValidWindow")
41  *        .notContains("ImaginaryWindow")
42  *        .showsAboveAppWindow("NavigationBar")
43  *        .forAllEntries()
44  * ```
45  *
46  * Example2:
47  * ```
48  *    val trace = WindowManagerTraceParser().parse(myTraceFile)
49  *    val subject = WindowManagerTraceSubject(trace) {
50  *        check(myCustomAssertion(this)) { "My assertion lazy message" }
51  *    }
52  * ```
53  */
54 class WindowManagerTraceSubject(
55     val trace: WindowManagerTrace,
56     override val reader: Reader? = null
57 ) :
58     FlickerTraceSubject<WindowManagerStateSubject>(),
59     IWindowManagerSubject<WindowManagerTraceSubject, RegionTraceSubject> {
60 
61     override val subjects by lazy {
62         trace.entries.map { WindowManagerStateSubject(it, reader, this) }
63     }
64 
65     /** {@inheritDoc} */
66     override fun then(): WindowManagerTraceSubject = apply { super.then() }
67 
68     /** {@inheritDoc} */
69     override fun skipUntilFirstAssertion(): WindowManagerTraceSubject = apply {
70         super.skipUntilFirstAssertion()
71     }
72 
73     /** {@inheritDoc} */
74     override fun isEmpty(): WindowManagerTraceSubject = apply {
75         check { "Trace is empty" }.that(trace.entries.isEmpty()).isEqual(true)
76     }
77 
78     /** {@inheritDoc} */
79     override fun isNotEmpty(): WindowManagerTraceSubject = apply {
80         check { "Trace is not empty" }.that(trace.entries.isEmpty()).isEqual(false)
81     }
82 
83     /**
84      * @return List of [WindowStateSubject]s matching [componentMatcher] in the order they
85      *
86      * ```
87      *      appear on the trace
88      *
89      * @param componentMatcher
90      * ```
91      *
92      * Components to search
93      */
94     fun windowStates(componentMatcher: IComponentMatcher): List<WindowStateSubject> = windowStates {
95         componentMatcher.windowMatchesAnyOf(it)
96     }
97 
98     /**
99      * @return List of [WindowStateSubject]s matching [predicate] in the order they
100      *
101      * ```
102      *      appear on the trace
103      *
104      * @param predicate
105      * ```
106      *
107      * To search
108      */
109     fun windowStates(predicate: (WindowState) -> Boolean): List<WindowStateSubject> {
110         return subjects.mapNotNull { it.windowState { window -> predicate(window) } }
111     }
112 
113     /** {@inheritDoc} */
114     override fun notContains(componentMatcher: IComponentMatcher): WindowManagerTraceSubject =
115         notContains(componentMatcher, isOptional = false)
116 
117     /** See [notContains] */
118     fun notContains(
119         componentMatcher: IComponentMatcher,
120         isOptional: Boolean
121     ): WindowManagerTraceSubject = apply {
122         addAssertion("notContains(${componentMatcher.toWindowIdentifier()})", isOptional) {
123             it.notContains(componentMatcher)
124         }
125     }
126 
127     /** {@inheritDoc} */
128     override fun isAboveAppWindowVisible(
129         componentMatcher: IComponentMatcher
130     ): WindowManagerTraceSubject = isAboveAppWindowVisible(componentMatcher, isOptional = false)
131 
132     /** See [isAboveAppWindowVisible] */
133     fun isAboveAppWindowVisible(
134         componentMatcher: IComponentMatcher,
135         isOptional: Boolean
136     ): WindowManagerTraceSubject = apply {
137         addAssertion(
138             "isAboveAppWindowVisible(${componentMatcher.toWindowIdentifier()})",
139             isOptional
140         ) {
141             it.isAboveAppWindowVisible(componentMatcher)
142         }
143     }
144 
145     /** {@inheritDoc} */
146     override fun isAboveAppWindowInvisible(
147         componentMatcher: IComponentMatcher
148     ): WindowManagerTraceSubject = isAboveAppWindowInvisible(componentMatcher, isOptional = false)
149 
150     /** See [isAboveAppWindowInvisible] */
151     fun isAboveAppWindowInvisible(
152         componentMatcher: IComponentMatcher,
153         isOptional: Boolean
154     ): WindowManagerTraceSubject = apply {
155         addAssertion(
156             "isAboveAppWindowInvisible(${componentMatcher.toWindowIdentifier()})",
157             isOptional
158         ) {
159             it.isAboveAppWindowInvisible(componentMatcher)
160         }
161     }
162 
163     /** {@inheritDoc} */
164     override fun isBelowAppWindowVisible(
165         componentMatcher: IComponentMatcher
166     ): WindowManagerTraceSubject = isBelowAppWindowVisible(componentMatcher, isOptional = false)
167 
168     /** See [isBelowAppWindowVisible] */
169     fun isBelowAppWindowVisible(
170         componentMatcher: IComponentMatcher,
171         isOptional: Boolean
172     ): WindowManagerTraceSubject = apply {
173         addAssertion(
174             "isBelowAppWindowVisible(${componentMatcher.toWindowIdentifier()})",
175             isOptional
176         ) {
177             it.isBelowAppWindowVisible(componentMatcher)
178         }
179     }
180 
181     /** {@inheritDoc} */
182     override fun isBelowAppWindowInvisible(
183         componentMatcher: IComponentMatcher
184     ): WindowManagerTraceSubject = isBelowAppWindowInvisible(componentMatcher, isOptional = false)
185 
186     /** See [isBelowAppWindowInvisible] */
187     fun isBelowAppWindowInvisible(
188         componentMatcher: IComponentMatcher,
189         isOptional: Boolean
190     ): WindowManagerTraceSubject = apply {
191         addAssertion(
192             "isBelowAppWindowInvisible(${componentMatcher.toWindowIdentifier()})",
193             isOptional
194         ) {
195             it.isBelowAppWindowInvisible(componentMatcher)
196         }
197     }
198 
199     /** {@inheritDoc} */
200     override fun isNonAppWindowVisible(
201         componentMatcher: IComponentMatcher
202     ): WindowManagerTraceSubject = isNonAppWindowVisible(componentMatcher, isOptional = false)
203 
204     /** See [isNonAppWindowVisible] */
205     fun isNonAppWindowVisible(
206         componentMatcher: IComponentMatcher,
207         isOptional: Boolean
208     ): WindowManagerTraceSubject = apply {
209         addAssertion(
210             "isNonAppWindowVisible(${componentMatcher.toWindowIdentifier()})",
211             isOptional
212         ) {
213             it.isNonAppWindowVisible(componentMatcher)
214         }
215     }
216 
217     /** {@inheritDoc} */
218     override fun isNonAppWindowInvisible(
219         componentMatcher: IComponentMatcher
220     ): WindowManagerTraceSubject = isNonAppWindowInvisible(componentMatcher, isOptional = false)
221 
222     /** See [isNonAppWindowInvisible] */
223     fun isNonAppWindowInvisible(
224         componentMatcher: IComponentMatcher,
225         isOptional: Boolean
226     ): WindowManagerTraceSubject = apply {
227         addAssertion(
228             "isNonAppWindowInvisible(${componentMatcher.toWindowIdentifier()})",
229             isOptional
230         ) {
231             it.isNonAppWindowInvisible(componentMatcher)
232         }
233     }
234 
235     /** {@inheritDoc} */
236     override fun isAppWindowOnTop(componentMatcher: IComponentMatcher): WindowManagerTraceSubject =
237         isAppWindowOnTop(componentMatcher, isOptional = false)
238 
239     /** See [isAppWindowOnTop] */
240     fun isAppWindowOnTop(
241         componentMatcher: IComponentMatcher,
242         isOptional: Boolean
243     ): WindowManagerTraceSubject = apply {
244         addAssertion("isAppWindowOnTop(${componentMatcher.toWindowIdentifier()})", isOptional) {
245             it.isAppWindowOnTop(componentMatcher)
246         }
247     }
248 
249     /** {@inheritDoc} */
250     override fun isAppWindowNotOnTop(
251         componentMatcher: IComponentMatcher
252     ): WindowManagerTraceSubject = isAppWindowNotOnTop(componentMatcher, isOptional = false)
253 
254     /** See [isAppWindowNotOnTop] */
255     fun isAppWindowNotOnTop(
256         componentMatcher: IComponentMatcher,
257         isOptional: Boolean
258     ): WindowManagerTraceSubject = apply {
259         addAssertion("appWindowNotOnTop(${componentMatcher.toWindowIdentifier()})", isOptional) {
260             it.isAppWindowNotOnTop(componentMatcher)
261         }
262     }
263 
264     /** {@inheritDoc} */
265     override fun isAppWindowVisible(
266         componentMatcher: IComponentMatcher
267     ): WindowManagerTraceSubject = isAppWindowVisible(componentMatcher, isOptional = false)
268 
269     /** See [isAppWindowVisible] */
270     fun isAppWindowVisible(
271         componentMatcher: IComponentMatcher,
272         isOptional: Boolean
273     ): WindowManagerTraceSubject = apply {
274         addAssertion("isAppWindowVisible(${componentMatcher.toWindowIdentifier()})", isOptional) {
275             it.isAppWindowVisible(componentMatcher)
276         }
277     }
278 
279     /** {@inheritDoc} */
280     override fun hasNoVisibleAppWindow(): WindowManagerTraceSubject =
281         hasNoVisibleAppWindow(isOptional = false)
282 
283     /** See [hasNoVisibleAppWindow] */
284     fun hasNoVisibleAppWindow(isOptional: Boolean): WindowManagerTraceSubject = apply {
285         addAssertion("hasNoVisibleAppWindow()", isOptional) { it.hasNoVisibleAppWindow() }
286     }
287 
288     /** {@inheritDoc} */
289     override fun isKeyguardShowing(): WindowManagerTraceSubject =
290         isKeyguardShowing(isOptional = false)
291 
292     /** See [isKeyguardShowing] */
293     fun isKeyguardShowing(isOptional: Boolean): WindowManagerTraceSubject = apply {
294         addAssertion("isKeyguardShowing()", isOptional) { it.isKeyguardShowing() }
295     }
296 
297     /** {@inheritDoc} */
298     override fun isAppSnapshotStartingWindowVisibleFor(
299         componentMatcher: IComponentMatcher
300     ): WindowManagerTraceSubject =
301         isAppSnapshotStartingWindowVisibleFor(componentMatcher, isOptional = false)
302 
303     /** See [isAppSnapshotStartingWindowVisibleFor] */
304     fun isAppSnapshotStartingWindowVisibleFor(
305         componentMatcher: IComponentMatcher,
306         isOptional: Boolean
307     ): WindowManagerTraceSubject = apply {
308         addAssertion(
309             "isAppSnapshotStartingWindowVisibleFor(${componentMatcher.toWindowIdentifier()})",
310             isOptional
311         ) {
312             it.isAppSnapshotStartingWindowVisibleFor(componentMatcher)
313         }
314     }
315 
316     /** {@inheritDoc} */
317     override fun isAppWindowInvisible(
318         componentMatcher: IComponentMatcher
319     ): WindowManagerTraceSubject = isAppWindowInvisible(componentMatcher, isOptional = false)
320 
321     /** See [isAppWindowInvisible] */
322     fun isAppWindowInvisible(
323         componentMatcher: IComponentMatcher,
324         isOptional: Boolean
325     ): WindowManagerTraceSubject = apply {
326         addAssertion("isAppWindowInvisible(${componentMatcher.toWindowIdentifier()})", isOptional) {
327             it.isAppWindowInvisible(componentMatcher)
328         }
329     }
330 
331     /** {@inheritDoc} */
332     override fun doNotOverlap(
333         vararg componentMatcher: IComponentMatcher
334     ): WindowManagerTraceSubject = apply {
335         val repr = componentMatcher.joinToString(", ") { it.toWindowIdentifier() }
336         addAssertion("noWindowsOverlap($repr)") { it.doNotOverlap(*componentMatcher) }
337     }
338 
339     /** {@inheritDoc} */
340     override fun isAboveWindow(
341         aboveWindowComponentMatcher: IComponentMatcher,
342         belowWindowComponentMatcher: IComponentMatcher
343     ): WindowManagerTraceSubject = apply {
344         val aboveWindowTitle = aboveWindowComponentMatcher.toWindowIdentifier()
345         val belowWindowTitle = belowWindowComponentMatcher.toWindowIdentifier()
346         addAssertion("$aboveWindowTitle is above $belowWindowTitle") {
347             it.isAboveWindow(aboveWindowComponentMatcher, belowWindowComponentMatcher)
348         }
349     }
350 
351     /** See [isAppWindowInvisible] */
352     override fun visibleRegion(componentMatcher: IComponentMatcher?): RegionTraceSubject {
353         val regionTrace =
354             RegionTrace(
355                 componentMatcher,
356                 subjects.map { it.visibleRegion(componentMatcher).regionEntry }
357             )
358 
359         return RegionTraceSubject(regionTrace, reader)
360     }
361 
362     /** {@inheritDoc} */
363     override fun contains(componentMatcher: IComponentMatcher): WindowManagerTraceSubject =
364         contains(componentMatcher, isOptional = false)
365 
366     /** See [contains] */
367     fun contains(
368         componentMatcher: IComponentMatcher,
369         isOptional: Boolean
370     ): WindowManagerTraceSubject = apply {
371         addAssertion("contains(${componentMatcher.toWindowIdentifier()})", isOptional) {
372             it.contains(componentMatcher)
373         }
374     }
375 
376     /** {@inheritDoc} */
377     override fun containsAboveAppWindow(
378         componentMatcher: IComponentMatcher
379     ): WindowManagerTraceSubject = containsAboveAppWindow(componentMatcher, isOptional = false)
380 
381     /** See [containsAboveAppWindow] */
382     fun containsAboveAppWindow(
383         componentMatcher: IComponentMatcher,
384         isOptional: Boolean
385     ): WindowManagerTraceSubject = apply {
386         addAssertion(
387             "containsAboveAppWindow(${componentMatcher.toWindowIdentifier()})",
388             isOptional
389         ) {
390             it.containsAboveAppWindow(componentMatcher)
391         }
392     }
393 
394     /** {@inheritDoc} */
395     override fun containsAppWindow(componentMatcher: IComponentMatcher): WindowManagerTraceSubject =
396         containsAppWindow(componentMatcher, isOptional = false)
397 
398     /** See [containsAppWindow] */
399     fun containsAppWindow(
400         componentMatcher: IComponentMatcher,
401         isOptional: Boolean
402     ): WindowManagerTraceSubject = apply {
403         addAssertion("containsAppWindow(${componentMatcher.toWindowIdentifier()})", isOptional) {
404             it.containsAboveAppWindow(componentMatcher)
405         }
406     }
407 
408     /** {@inheritDoc} */
409     override fun containsBelowAppWindow(
410         componentMatcher: IComponentMatcher
411     ): WindowManagerTraceSubject = containsBelowAppWindow(componentMatcher, isOptional = false)
412 
413     /** See [containsBelowAppWindow] */
414     fun containsBelowAppWindow(
415         componentMatcher: IComponentMatcher,
416         isOptional: Boolean
417     ): WindowManagerTraceSubject = apply {
418         addAssertion(
419             "containsBelowAppWindows(${componentMatcher.toWindowIdentifier()})",
420             isOptional
421         ) {
422             it.containsBelowAppWindow(componentMatcher)
423         }
424     }
425 
426     /** {@inheritDoc} */
427     override fun containsNonAppWindow(
428         componentMatcher: IComponentMatcher
429     ): WindowManagerTraceSubject = containsNonAppWindow(componentMatcher, isOptional = false)
430 
431     /** See [containsNonAppWindow] */
432     fun containsNonAppWindow(
433         componentMatcher: IComponentMatcher,
434         isOptional: Boolean
435     ): WindowManagerTraceSubject = apply {
436         addAssertion("containsNonAppWindow(${componentMatcher.toWindowIdentifier()})", isOptional) {
437             it.containsNonAppWindow(componentMatcher)
438         }
439     }
440 
441     /** {@inheritDoc} */
442     override fun isHomeActivityInvisible(): WindowManagerTraceSubject =
443         isHomeActivityInvisible(isOptional = false)
444 
445     /** See [isHomeActivityInvisible] */
446     fun isHomeActivityInvisible(isOptional: Boolean): WindowManagerTraceSubject = apply {
447         addAssertion("isHomeActivityInvisible", isOptional) { it.isHomeActivityInvisible() }
448     }
449 
450     /** {@inheritDoc} */
451     override fun isHomeActivityVisible(): WindowManagerTraceSubject =
452         isHomeActivityVisible(isOptional = false)
453 
454     /** See [isHomeActivityVisible] */
455     fun isHomeActivityVisible(isOptional: Boolean): WindowManagerTraceSubject = apply {
456         addAssertion("isHomeActivityVisible", isOptional) { it.isHomeActivityVisible() }
457     }
458 
459     /** {@inheritDoc} */
460     override fun hasRotation(rotation: Rotation, displayId: Int): WindowManagerTraceSubject =
461         hasRotation(rotation, displayId, isOptional = false)
462 
463     /** See [hasRotation] */
464     fun hasRotation(
465         rotation: Rotation,
466         displayId: Int,
467         isOptional: Boolean
468     ): WindowManagerTraceSubject = apply {
469         addAssertion("hasRotation($rotation, display=$displayId)", isOptional) {
470             it.hasRotation(rotation, displayId)
471         }
472     }
473 
474     /** {@inheritDoc} */
475     override fun isNotPinned(componentMatcher: IComponentMatcher): WindowManagerTraceSubject =
476         isNotPinned(componentMatcher, isOptional = false)
477 
478     /** See [isNotPinned] */
479     fun isNotPinned(
480         componentMatcher: IComponentMatcher,
481         isOptional: Boolean
482     ): WindowManagerTraceSubject = apply {
483         addAssertion("isNotPinned(${componentMatcher.toWindowIdentifier()})", isOptional) {
484             it.isNotPinned(componentMatcher)
485         }
486     }
487 
488     /** {@inheritDoc} */
489     override fun isFocusedApp(app: String): WindowManagerTraceSubject =
490         isFocusedApp(app, isOptional = false)
491 
492     /** See [isFocusedApp] */
493     fun isFocusedApp(app: String, isOptional: Boolean): WindowManagerTraceSubject = apply {
494         addAssertion("isFocusedApp($app)", isOptional) { it.isFocusedApp(app) }
495     }
496 
497     /** {@inheritDoc} */
498     override fun isNotFocusedApp(app: String): WindowManagerTraceSubject =
499         isNotFocusedApp(app, isOptional = false)
500 
501     /** See [isNotFocusedApp] */
502     fun isNotFocusedApp(app: String, isOptional: Boolean): WindowManagerTraceSubject = apply {
503         addAssertion("isNotFocusedApp($app)", isOptional) { it.isNotFocusedApp(app) }
504     }
505 
506     /** {@inheritDoc} */
507     override fun isPinned(componentMatcher: IComponentMatcher): WindowManagerTraceSubject =
508         isPinned(componentMatcher, isOptional = false)
509 
510     /** See [isPinned] */
511     fun isPinned(
512         componentMatcher: IComponentMatcher,
513         isOptional: Boolean
514     ): WindowManagerTraceSubject = apply {
515         addAssertion("isPinned(${componentMatcher.toWindowIdentifier()})", isOptional) {
516             it.isPinned(componentMatcher)
517         }
518     }
519 
520     /** {@inheritDoc} */
521     override fun isRecentsActivityInvisible(): WindowManagerTraceSubject =
522         isRecentsActivityInvisible(isOptional = false)
523 
524     /** See [isRecentsActivityInvisible] */
525     fun isRecentsActivityInvisible(isOptional: Boolean): WindowManagerTraceSubject = apply {
526         addAssertion("isRecentsActivityInvisible", isOptional) { it.isRecentsActivityInvisible() }
527     }
528 
529     /** {@inheritDoc} */
530     override fun isRecentsActivityVisible(): WindowManagerTraceSubject =
531         isRecentsActivityVisible(isOptional = false)
532 
533     /** See [isRecentsActivityVisible] */
534     fun isRecentsActivityVisible(isOptional: Boolean): WindowManagerTraceSubject = apply {
535         addAssertion("isRecentsActivityVisible", isOptional) { it.isRecentsActivityVisible() }
536     }
537 
538     override fun isValid(): WindowManagerTraceSubject = apply {
539         addAssertion("isValid") { it.isValid() }
540     }
541 
542     /** {@inheritDoc} */
543     override fun notContainsAppWindow(
544         componentMatcher: IComponentMatcher
545     ): WindowManagerTraceSubject = notContainsAppWindow(componentMatcher, isOptional = false)
546 
547     /** See [notContainsAppWindow] */
548     fun notContainsAppWindow(
549         componentMatcher: IComponentMatcher,
550         isOptional: Boolean
551     ): WindowManagerTraceSubject = apply {
552         addAssertion("notContainsAppWindow(${componentMatcher.toWindowIdentifier()})", isOptional) {
553             it.notContainsAppWindow(componentMatcher)
554         }
555     }
556 
557     /** {@inheritDoc} */
558     override fun containsAtLeastOneDisplay(): WindowManagerTraceSubject = apply {
559         addAssertion("containAtLeastOneDisplay", isOptional = false) {
560             it.containsAtLeastOneDisplay()
561         }
562     }
563 
564     /** Checks that all visible layers are shown for more than one consecutive entry */
565     fun visibleWindowsShownMoreThanOneConsecutiveEntry(
566         ignoreWindows: List<ComponentNameMatcher> =
567             listOf(
568                 ComponentNameMatcher.SPLASH_SCREEN,
569                 ComponentNameMatcher.SNAPSHOT,
570                 ComponentNameMatcher.SECONDARY_HOME_HANDLE,
571                 ComponentNameMatcher.EDGE_BACK_GESTURE_HANDLER,
572             )
573     ): WindowManagerTraceSubject = apply {
574         visibleEntriesShownMoreThanOneConsecutiveTime { subject ->
575             subject.wmState.windowStates
576                 .filter { it.isVisible }
577                 .filter { window ->
578                     ignoreWindows.none { matcher -> matcher.windowMatchesAnyOf(window) }
579                 }
580                 .map { it.name }
581                 .toSet()
582         }
583     }
584 
585     /** Executes a custom [assertion] on the current subject */
586     operator fun invoke(
587         name: String,
588         isOptional: Boolean = false,
589         assertion: (WindowManagerStateSubject) -> Unit
590     ): WindowManagerTraceSubject = apply { addAssertion(name, isOptional, assertion) }
591 
592     /** Run the assertions for all trace entries within the specified time range */
593     fun forElapsedTimeRange(startTime: Long, endTime: Long) {
594         val subjectsInRange =
595             subjects.filter { it.wmState.timestamp.elapsedNanos in startTime..endTime }
596         assertionsChecker.test(subjectsInRange)
597     }
598 
599     /**
600      * User-defined entry point for the trace entry with [timestamp]
601      *
602      * @param timestamp of the entry
603      */
604     fun getEntryByElapsedTimestamp(timestamp: Long): WindowManagerStateSubject =
605         subjects.first { it.wmState.timestamp.elapsedNanos == timestamp }
606 }
607