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.layers
18 
19 import android.tools.flicker.subject.FlickerTraceSubject
20 import android.tools.flicker.subject.exceptions.ExceptionMessageBuilder
21 import android.tools.flicker.subject.exceptions.InvalidElementException
22 import android.tools.flicker.subject.exceptions.InvalidPropertyException
23 import android.tools.flicker.subject.region.RegionTraceSubject
24 import android.tools.io.Reader
25 import android.tools.traces.component.ComponentNameMatcher
26 import android.tools.traces.component.EdgeExtensionComponentMatcher
27 import android.tools.traces.component.IComponentMatcher
28 import android.tools.traces.component.IComponentNameMatcher
29 import android.tools.traces.region.RegionTrace
30 import android.tools.traces.surfaceflinger.Layer
31 import android.tools.traces.surfaceflinger.LayersTrace
32 
33 /**
34  * Subject for [LayersTrace] objects, used to make assertions over behaviors that occur throughout a
35  * whole trace
36  *
37  * To make assertions over a trace it is recommended to create a subject using
38  * [LayersTraceSubject](myTrace).
39  *
40  * Example:
41  * ```
42  *    val trace = LayersTraceParser().parse(myTraceFile)
43  *    val subject = LayersTraceSubject(trace)
44  *        .contains("ValidLayer")
45  *        .notContains("ImaginaryLayer")
46  *        .coversExactly(DISPLAY_AREA)
47  *        .forAllEntries()
48  * ```
49  *
50  * Example2:
51  * ```
52  *    val trace = LayersTraceParser().parse(myTraceFile)
53  *    val subject = LayersTraceSubject(trace) {
54  *        check("Custom check") { myCustomAssertion(this) }
55  *    }
56  * ```
57  */
58 class LayersTraceSubject(val trace: LayersTrace, override val reader: Reader? = null) :
59     FlickerTraceSubject<LayerTraceEntrySubject>(),
60     ILayerSubject<LayersTraceSubject, RegionTraceSubject> {
61 
62     override val subjects by lazy {
63         trace.entries.map { LayerTraceEntrySubject(it, reader, trace) }
64     }
65 
66     /** {@inheritDoc} */
67     override fun then(): LayersTraceSubject = apply { super.then() }
68 
69     /** {@inheritDoc} */
70     override fun isEmpty(): LayersTraceSubject = apply {
71         check { "Trace is empty" }.that(trace.entries.isEmpty()).isEqual(true)
72     }
73 
74     /** {@inheritDoc} */
75     override fun isNotEmpty(): LayersTraceSubject = apply {
76         check { "Trace is not empty" }.that(trace.entries.isNotEmpty()).isEqual(true)
77     }
78 
79     /** {@inheritDoc} */
80     override fun layer(name: String, frameNumber: Long): LayerSubject {
81         val result = subjects.firstNotNullOfOrNull { it.layer(name, frameNumber) }
82 
83         if (result == null) {
84             val errorMsgBuilder =
85                 ExceptionMessageBuilder()
86                     .forSubject(this)
87                     .forInvalidElement(name, expectElementExists = true)
88                     .addExtraDescription("Frame number", frameNumber)
89             throw InvalidElementException(errorMsgBuilder)
90         }
91 
92         return result
93     }
94 
95     /** @return List of [LayerSubject]s matching [name] in the order they appear on the trace */
96     fun layers(name: String): List<LayerSubject> =
97         subjects.mapNotNull { it.layer { layer -> layer.name.contains(name) } }
98 
99     /**
100      * @return List of [LayerSubject]s matching [predicate] in the order they appear on the trace
101      */
102     fun layers(predicate: (Layer) -> Boolean): List<LayerSubject> =
103         subjects.mapNotNull { it.layer { layer -> predicate(layer) } }
104 
105     /** Checks that all visible layers are shown for more than one consecutive entry */
106     fun visibleLayersShownMoreThanOneConsecutiveEntry(
107         ignoreLayers: List<IComponentMatcher> = VISIBLE_FOR_MORE_THAN_ONE_ENTRY_IGNORE_LAYERS
108     ): LayersTraceSubject = apply {
109         visibleEntriesShownMoreThanOneConsecutiveTime { subject ->
110             subject.entry.visibleLayers
111                 .filter { visibleLayer ->
112                     ignoreLayers.none { matcher -> matcher.layerMatchesAnyOf(visibleLayer) }
113                 }
114                 .map { it.name }
115                 .toSet()
116         }
117     }
118 
119     /** {@inheritDoc} */
120     override fun notContains(componentMatcher: IComponentMatcher): LayersTraceSubject =
121         notContains(componentMatcher, isOptional = false)
122 
123     /** See [notContains] */
124     fun notContains(componentMatcher: IComponentMatcher, isOptional: Boolean): LayersTraceSubject =
125         apply {
126             addAssertion("notContains(${componentMatcher.toLayerIdentifier()})", isOptional) {
127                 it.notContains(componentMatcher)
128             }
129         }
130 
131     /** {@inheritDoc} */
132     override fun contains(componentMatcher: IComponentMatcher): LayersTraceSubject =
133         contains(componentMatcher, isOptional = false)
134 
135     /** See [contains] */
136     fun contains(componentMatcher: IComponentMatcher, isOptional: Boolean): LayersTraceSubject =
137         apply {
138             addAssertion("contains(${componentMatcher.toLayerIdentifier()})", isOptional) {
139                 it.contains(componentMatcher)
140             }
141         }
142 
143     /** {@inheritDoc} */
144     override fun isVisible(componentMatcher: IComponentMatcher): LayersTraceSubject =
145         isVisible(componentMatcher, isOptional = false)
146 
147     /** See [isVisible] */
148     fun isVisible(componentMatcher: IComponentMatcher, isOptional: Boolean): LayersTraceSubject =
149         apply {
150             addAssertion("isVisible(${componentMatcher.toLayerIdentifier()})", isOptional) {
151                 it.isVisible(componentMatcher)
152             }
153         }
154 
155     /** {@inheritDoc} */
156     override fun isInvisible(componentMatcher: IComponentMatcher): LayersTraceSubject =
157         isInvisible(componentMatcher, isOptional = false)
158 
159     /** See [isInvisible] */
160     fun isInvisible(componentMatcher: IComponentMatcher, isOptional: Boolean): LayersTraceSubject =
161         apply {
162             addAssertion("isInvisible(${componentMatcher.toLayerIdentifier()})", isOptional) {
163                 it.isInvisible(componentMatcher)
164             }
165         }
166 
167     /** {@inheritDoc} */
168     override fun isSplashScreenVisibleFor(
169         componentMatcher: IComponentNameMatcher
170     ): LayersTraceSubject = isSplashScreenVisibleFor(componentMatcher, isOptional = false)
171 
172     /** {@inheritDoc} */
173     override fun hasColor(componentMatcher: IComponentMatcher): LayersTraceSubject = apply {
174         addAssertion("hasColor(${componentMatcher.toLayerIdentifier()})") {
175             it.hasColor(componentMatcher)
176         }
177     }
178 
179     /** {@inheritDoc} */
180     override fun hasNoColor(componentMatcher: IComponentMatcher): LayersTraceSubject = apply {
181         addAssertion("hasNoColor(${componentMatcher.toLayerIdentifier()})") {
182             it.hasNoColor(componentMatcher)
183         }
184     }
185 
186     /** {@inheritDoc} */
187     override fun hasRoundedCorners(componentMatcher: IComponentMatcher): LayersTraceSubject =
188         apply {
189             addAssertion("hasRoundedCorners(${componentMatcher.toLayerIdentifier()})") {
190                 it.hasRoundedCorners(componentMatcher)
191             }
192         }
193 
194     /** See [isSplashScreenVisibleFor] */
195     fun isSplashScreenVisibleFor(
196         componentMatcher: IComponentNameMatcher,
197         isOptional: Boolean
198     ): LayersTraceSubject = apply {
199         addAssertion(
200             "isSplashScreenVisibleFor(${componentMatcher.toLayerIdentifier()})",
201             isOptional
202         ) {
203             it.isSplashScreenVisibleFor(componentMatcher)
204         }
205     }
206 
207     /** See [visibleRegion] */
208     fun visibleRegion(): RegionTraceSubject =
209         visibleRegion(componentMatcher = null, useCompositionEngineRegionOnly = false)
210 
211     /** See [visibleRegion] */
212     fun visibleRegion(componentMatcher: IComponentMatcher?): RegionTraceSubject =
213         visibleRegion(componentMatcher, useCompositionEngineRegionOnly = false)
214 
215     /** {@inheritDoc} */
216     override fun visibleRegion(
217         componentMatcher: IComponentMatcher?,
218         useCompositionEngineRegionOnly: Boolean
219     ): RegionTraceSubject {
220         val regionTrace =
221             RegionTrace(
222                 componentMatcher,
223                 subjects.map {
224                     it.visibleRegion(componentMatcher, useCompositionEngineRegionOnly).regionEntry
225                 }
226             )
227         return RegionTraceSubject(regionTrace, reader)
228     }
229 
230     fun atLeastOneEntryContainsOneDisplayOn(): LayersTraceSubject = apply {
231         isNotEmpty()
232         val anyEntryWithDisplayOn =
233             subjects.any { it.entry.displays.any { display -> display.isOn } }
234         if (!anyEntryWithDisplayOn) {
235             val errorMsgBuilder =
236                 ExceptionMessageBuilder()
237                     .forInvalidProperty("Display")
238                     .setExpected("At least one display On in any entry")
239                     .setActual("No displays on in any entry")
240             throw InvalidPropertyException(errorMsgBuilder)
241         }
242     }
243 
244     override fun containsAtLeastOneDisplay(): LayersTraceSubject = apply {
245         addAssertion("containAtLeastOneDisplay", isOptional = false) {
246             it.containsAtLeastOneDisplay()
247         }
248     }
249 
250     /** Executes a custom [assertion] on the current subject */
251     operator fun invoke(
252         name: String,
253         isOptional: Boolean = false,
254         assertion: (LayerTraceEntrySubject) -> Unit
255     ): LayersTraceSubject = apply { addAssertion(name, isOptional, assertion) }
256 
257     fun hasFrameSequence(name: String, frameNumbers: Iterable<Long>): LayersTraceSubject =
258         hasFrameSequence(ComponentNameMatcher("", name), frameNumbers)
259 
260     fun hasFrameSequence(
261         componentMatcher: IComponentMatcher,
262         frameNumbers: Iterable<Long>
263     ): LayersTraceSubject = apply {
264         val firstFrame = frameNumbers.first()
265         val entries =
266             trace.entries
267                 .asSequence()
268                 // map entry to buffer layers with name
269                 .map { it.getLayerWithBuffer(componentMatcher) }
270                 // removing all entries without the layer
271                 .filterNotNull()
272                 // removing all entries with the same frame number
273                 .distinctBy { it.currFrame }
274                 // drop until the first frame we are interested in
275                 .dropWhile { layer -> layer.currFrame != firstFrame }
276 
277         var numFound = 0
278         val frameNumbersMatch =
279             entries
280                 .zip(frameNumbers.asSequence()) { layer, frameNumber ->
281                     numFound++
282                     layer.currFrame == frameNumber
283                 }
284                 .all { it }
285         val allFramesFound = frameNumbers.count() == numFound
286         if (!allFramesFound || !frameNumbersMatch) {
287             val errorMsgBuilder =
288                 ExceptionMessageBuilder()
289                     .forSubject(this)
290                     .forInvalidElement(
291                         componentMatcher.toLayerIdentifier(),
292                         expectElementExists = true
293                     )
294                     .addExtraDescription("With frame sequence", frameNumbers.joinToString(","))
295             throw InvalidElementException(errorMsgBuilder)
296         }
297     }
298 
299     /** Run the assertions for all trace entries within the specified time range */
300     fun forSystemUpTimeRange(startTime: Long, endTime: Long) {
301         val subjectsInRange =
302             subjects.filter { it.entry.timestamp.systemUptimeNanos in startTime..endTime }
303         assertionsChecker.test(subjectsInRange)
304     }
305 
306     /**
307      * User-defined entry point for the trace entry with [timestamp]
308      *
309      * @param timestamp of the entry
310      */
311     fun getEntryBySystemUpTime(
312         timestamp: Long,
313         byElapsedTimestamp: Boolean = false
314     ): LayerTraceEntrySubject {
315         return if (byElapsedTimestamp) {
316             subjects.first { it.entry.elapsedTimestamp == timestamp }
317         } else {
318             subjects.first { it.entry.timestamp.systemUptimeNanos == timestamp }
319         }
320     }
321 
322     companion object {
323         val VISIBLE_FOR_MORE_THAN_ONE_ENTRY_IGNORE_LAYERS =
324             listOf(
325                 ComponentNameMatcher.SPLASH_SCREEN,
326                 ComponentNameMatcher.SNAPSHOT,
327                 ComponentNameMatcher.IME_SNAPSHOT,
328                 ComponentNameMatcher.PIP_CONTENT_OVERLAY,
329                 ComponentNameMatcher.EDGE_BACK_GESTURE_HANDLER,
330                 ComponentNameMatcher.COLOR_FADE,
331                 ComponentNameMatcher.TRANSITION_SNAPSHOT,
332                 ComponentNameMatcher.FLOATING_ROTATION_BUTTON,
333                 ComponentNameMatcher.WIRED_CHARGING_ANIMATION,
334                 EdgeExtensionComponentMatcher()
335             )
336     }
337 }
338