• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2021 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 com.android.server.wm.flicker.traces.layers
18 
19 import com.android.server.wm.flicker.assertions.Assertion
20 import com.android.server.wm.flicker.assertions.FlickerSubject
21 import com.android.server.wm.flicker.traces.FlickerFailureStrategy
22 import com.android.server.wm.flicker.traces.FlickerSubjectException
23 import com.android.server.wm.flicker.traces.region.RegionSubject
24 import com.android.server.wm.traces.common.FlickerComponentName
25 import com.android.server.wm.traces.common.layers.Layer
26 import com.android.server.wm.traces.common.layers.BaseLayerTraceEntry
27 import com.android.server.wm.traces.common.layers.LayersTrace
28 import com.google.common.truth.ExpectFailure
29 import com.google.common.truth.Fact
30 import com.google.common.truth.FailureMetadata
31 import com.google.common.truth.FailureStrategy
32 import com.google.common.truth.StandardSubjectBuilder
33 import com.google.common.truth.Subject
34 
35 /**
36  * Truth subject for [BaseLayerTraceEntry] objects, used to make assertions over behaviors that
37  * occur on a single SurfaceFlinger state.
38  *
39  * To make assertions over a specific state from a trace it is recommended to create a subject
40  * using [LayersTraceSubject.assertThat](myTrace) and select the specific state using:
41  *     [LayersTraceSubject.first]
42  *     [LayersTraceSubject.last]
43  *     [LayersTraceSubject.entry]
44  *
45  * Alternatively, it is also possible to use [LayerTraceEntrySubject.assertThat](myState) or
46  * Truth.assertAbout([LayerTraceEntrySubject.getFactory]), however they will provide less debug
47  * information because it uses Truth's default [FailureStrategy].
48  *
49  * Example:
50  *    val trace = LayersTraceParser.parseFromTrace(myTraceFile)
51  *    val subject = LayersTraceSubject.assertThat(trace).first()
52  *        .contains("ValidLayer")
53  *        .notContains("ImaginaryLayer")
54  *        .coversExactly(DISPLAY_AREA)
55  *        .invoke { myCustomAssertion(this) }
56  */
57 class LayerTraceEntrySubject private constructor(
58     fm: FailureMetadata,
59     val entry: BaseLayerTraceEntry,
60     val trace: LayersTrace?,
61     override val parent: FlickerSubject?
62 ) : FlickerSubject(fm, entry) {
63     override val timestamp: Long get() = entry.timestamp
64     override val selfFacts = listOf(Fact.fact("Entry", entry))
65 
66     val subjects by lazy {
67         entry.flattenedLayers.map { LayerSubject.assertThat(it, this, timestamp) }
68     }
69 
70     /**
71      * Executes a custom [assertion] on the current subject
72      */
73     operator fun invoke(assertion: Assertion<BaseLayerTraceEntry>): LayerTraceEntrySubject =
74         apply {
75             assertion(this.entry)
76         }
77 
78     /**
79      * Asserts that the current SurfaceFlinger state doesn't contain layers
80      */
81     fun isEmpty(): LayerTraceEntrySubject = apply {
82         check("Entry should not be empty")
83             .that(entry.flattenedLayers)
84             .isEmpty()
85     }
86 
87     /**
88      * Asserts that the current SurfaceFlinger state contains layers
89      */
90     fun isNotEmpty(): LayerTraceEntrySubject = apply {
91         check("Entry should not be empty")
92             .that(entry.flattenedLayers)
93             .isNotEmpty()
94     }
95 
96     /**
97      * Obtains the region occupied by all layers with name containing [components]
98      *
99      * @param components Components to search for
100      * @param useCompositionEngineRegionOnly If true, uses only the region calculated from the
101      *   Composition Engine (CE) -- visibleRegion in the proto definition. Otherwise calculates
102      *   the visible region when the information is not available from the CE
103      */
104     @JvmOverloads
105     fun visibleRegion(
106         vararg components: FlickerComponentName,
107         useCompositionEngineRegionOnly: Boolean = true
108     ): RegionSubject {
109         val layerNames = components.map { it.toLayerName() }
110         val selectedLayers = if (components.isEmpty()) {
111             // No filters so use all subjects
112             subjects
113         } else {
114             subjects.filter {
115                 subject -> layerNames.any {
116                     layerName -> subject.name.contains(layerName)
117                 }
118             }
119         }
120 
121         if (selectedLayers.isEmpty()) {
122             val str = if (layerNames.isNotEmpty()) layerNames.joinToString() else "<any>"
123             fail(listOf(
124                 Fact.fact(ASSERTION_TAG, "visibleRegion($str)"),
125                 Fact.fact("Use composition engine region", useCompositionEngineRegionOnly),
126                 Fact.fact("Could not find layers", str))
127             )
128         }
129 
130         val visibleLayers = selectedLayers.filter { it.isVisible }
131         return if (useCompositionEngineRegionOnly) {
132             val visibleAreas = visibleLayers.mapNotNull { it.layer?.visibleRegion }.toTypedArray()
133             RegionSubject.assertThat(visibleAreas, this, timestamp)
134         } else {
135             val visibleAreas = visibleLayers.mapNotNull { it.layer?.screenBounds }.toTypedArray()
136             RegionSubject.assertThat(visibleAreas, this, timestamp)
137         }
138     }
139 
140     /**
141      * Asserts the state contains a [Layer] with [Layer.name] containing [component].
142      *
143      * @param component Name of the layers to search
144      */
145     fun contains(component: FlickerComponentName): LayerTraceEntrySubject = apply {
146         val layerName = component.toLayerName()
147         val found = entry.flattenedLayers.any { it.name.contains(layerName) }
148         if (!found) {
149             fail(Fact.fact(ASSERTION_TAG, "contains(${component.toLayerName()})"),
150                 Fact.fact("Could not find", layerName))
151         }
152     }
153 
154     /**
155      * Asserts the state doesn't contain a [Layer] with [Layer.name] containing any of
156      *
157      * @param component Name of the layers to search
158      */
159     fun notContains(component: FlickerComponentName): LayerTraceEntrySubject = apply {
160         val layerName = component.toLayerName()
161         val foundEntry = subjects.firstOrNull { it.name.contains(layerName) }
162         foundEntry?.fail(Fact.fact(ASSERTION_TAG, "notContains(${component.toLayerName()})"),
163             Fact.fact("Could find", foundEntry))
164     }
165 
166     /**
167      * Asserts that a [Layer] with [Layer.name] containing [component] is visible.
168      *
169      * @param component Name of the layers to search
170      */
171     fun isVisible(component: FlickerComponentName): LayerTraceEntrySubject = apply {
172         contains(component)
173         var target: FlickerSubject? = null
174         var reason = listOf<Fact>()
175         val layerName = component.toLayerName()
176         val filteredLayers = subjects
177             .filter { it.name.contains(layerName) }
178         for (layer in filteredLayers) {
179             if (layer.layer?.isHiddenByParent == true) {
180                 reason = listOf(Fact.fact("Hidden by parent", layer.layer.parent?.name))
181                 target = layer
182                 continue
183             }
184             if (layer.isInvisible) {
185                 reason = layer.layer?.visibilityReason
186                     ?.map { Fact.fact("Is Invisible", it) }
187                     ?: emptyList()
188                 target = layer
189                 continue
190             }
191             reason = emptyList()
192             target = null
193             break
194         }
195 
196         if (reason.isNotEmpty()) {
197             target?.fail(
198                 Fact.fact(ASSERTION_TAG, "isVisible(${component.toLayerName()})"),
199                 *reason.toTypedArray())
200         }
201     }
202 
203     /**
204      * Asserts that a [Layer] with [Layer.name] containing [component] doesn't exist or
205      * is invisible.
206      *
207      * @param component Name of the layers to search
208      */
209     fun isInvisible(component: FlickerComponentName): LayerTraceEntrySubject = apply {
210         try {
211             isVisible(component)
212         } catch (e: FlickerSubjectException) {
213             val cause = e.cause
214             require(cause is AssertionError)
215             ExpectFailure.assertThat(cause).factKeys().isNotEmpty()
216             return@apply
217         }
218         val layerName = component.toLayerName()
219         val foundEntry = subjects
220                 .firstOrNull { it.name.contains(layerName) && it.isVisible }
221         foundEntry?.fail(Fact.fact(ASSERTION_TAG, "isInvisible(${component.toLayerName()})"),
222             Fact.fact("Is visible", foundEntry))
223     }
224 
225     /**
226      * Asserts that the entry contains a visible splash screen [Layer] for a [layer] with
227      * [Layer.name] containing any of [component].
228      *
229      * @param component Name of the layer to search
230      */
231     fun isSplashScreenVisibleFor(component: FlickerComponentName): LayerTraceEntrySubject = apply {
232         var target: FlickerSubject? = null
233         var reason: Fact? = null
234         val layerActivityRecordFilter = component.toActivityRecordFilter()
235         val filteredLayers = subjects
236                 .filter { layerActivityRecordFilter.containsMatchIn(it.name) }
237 
238         if (filteredLayers.isEmpty()) {
239             fail(Fact.fact(ASSERTION_TAG, "isSplashScreenVisibleFor(${component.toLayerName()})"),
240                 Fact.fact("Could not find Activity Record layer", component.toShortWindowName()))
241             return this
242         }
243 
244         // Check the matched activity record layers for containing splash screens
245         for (layer in filteredLayers) {
246             val splashScreenContainers =
247                 layer.layer?.children?.filter { it.name.contains("Splash Screen") }
248             val splashScreenLayers = splashScreenContainers?.flatMap {
249                 it.children.filter { childLayer ->
250                     childLayer.name.contains("Splash Screen")
251                 }
252             }
253 
254             if (splashScreenLayers?.all { it.isHiddenByParent || !it.isVisible } ?: true) {
255                 reason = Fact.fact("No splash screen visible for", layer.name)
256                 target = layer
257                 continue
258             }
259             reason = null
260             target = null
261             break
262         }
263 
264         reason?.run {
265             target?.fail(
266                 Fact.fact(ASSERTION_TAG, "isSplashScreenVisibleFor(${component.toLayerName()})"),
267                 reason)
268         }
269     }
270 
271     /**
272      * Obtains a [LayerSubject] for the first occurrence of a [Layer] with [Layer.name]
273      * containing [component].
274      * Always returns a subject, event when the layer doesn't exist. To verify if layer
275      * actually exists in the hierarchy use [LayerSubject.exists] or [LayerSubject.doesNotExist]
276      *
277      * @return LayerSubject that can be used to make assertions on a single layer matching
278      */
279     fun layer(component: FlickerComponentName): LayerSubject {
280         val name = component.toLayerName()
281         return layer {
282             it.name.contains(name)
283         }
284     }
285 
286     /**
287      * Obtains a [LayerSubject] for the first occurrence of a [Layer] with [Layer.name]
288      * containing [name] in [frameNumber].
289      *
290      * Always returns a subject, event when the layer doesn't exist. To verify if layer
291      * actually exists in the hierarchy use [LayerSubject.exists] or [LayerSubject.doesNotExist]
292      *
293      * @return LayerSubject that can be used to make assertions on a single layer matching
294      * [name] and [frameNumber].
295      */
296     fun layer(name: String, frameNumber: Long): LayerSubject {
297         return layer(name) {
298             it.name.contains(name) && it.currFrame == frameNumber
299         }
300     }
301 
302     /**
303      * Obtains a [LayerSubject] for the first occurrence of a [Layer] matching [predicate]
304      *
305      * Always returns a subject, event when the layer doesn't exist. To verify if layer
306      * actually exists in the hierarchy use [LayerSubject.exists] or [LayerSubject.doesNotExist]
307      *
308      * @param predicate to search for a layer
309      * @param name Name of the subject to use when not found (optional)
310      *
311      * @return [LayerSubject] that can be used to make assertions
312      */
313     @JvmOverloads
314     fun layer(name: String = "", predicate: (Layer) -> Boolean): LayerSubject {
315         return subjects.firstOrNull {
316             it.layer?.run { predicate(this) } ?: false
317         } ?: LayerSubject.assertThat(name, this, timestamp)
318     }
319 
320     override fun toString(): String {
321         return "LayerTraceEntrySubject($entry)"
322     }
323 
324     companion object {
325         /**
326          * Boiler-plate Subject.Factory for LayersTraceSubject
327          */
328         private fun getFactory(
329             trace: LayersTrace?,
330             parent: FlickerSubject?
331         ): Factory<Subject, BaseLayerTraceEntry> =
332             Factory { fm, subject -> LayerTraceEntrySubject(fm, subject, trace, parent) }
333 
334         /**
335          * Creates a [LayerTraceEntrySubject] to representing a SurfaceFlinger state[entry],
336          * which can be used to make assertions.
337          *
338          * @param entry SurfaceFlinger trace entry
339          * @param parent Trace that contains this entry (optional)
340          */
341         @JvmStatic
342         @JvmOverloads
343         fun assertThat(
344             entry: BaseLayerTraceEntry,
345             trace: LayersTrace? = null,
346             parent: FlickerSubject? = null
347         ): LayerTraceEntrySubject {
348             val strategy = FlickerFailureStrategy()
349             val subject = StandardSubjectBuilder.forCustomFailureStrategy(strategy)
350                 .about(getFactory(trace, parent))
351                 .that(entry) as LayerTraceEntrySubject
352             strategy.init(subject)
353             return subject
354         }
355 
356         /**
357          * Static method for getting the subject factory (for use with assertAbout())
358          */
359         @JvmStatic
360         @JvmOverloads
361         fun entries(
362             trace: LayersTrace? = null,
363             parent: FlickerSubject? = null
364         ): Factory<Subject, BaseLayerTraceEntry> = getFactory(trace, parent)
365     }
366 }