xref: /aosp_15_r20/platform_testing/libraries/flicker/utils/src/android/tools/traces/ConditionsFactory.kt (revision dd0948b35e70be4c0246aabd6c72554a5eb8b22a)
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.traces
18 
19 import android.content.Context
20 import android.content.res.Resources
21 import android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY
22 import android.hardware.devicestate.DeviceStateManager
23 import android.hardware.devicestate.feature.flags.Flags as DeviceStateManagerFlags
24 import android.tools.PlatformConsts
25 import android.tools.Rotation
26 import android.tools.traces.component.ComponentNameMatcher
27 import android.tools.traces.component.IComponentMatcher
28 import android.tools.traces.surfaceflinger.Layer
29 import android.tools.traces.surfaceflinger.Transform
30 import android.tools.traces.surfaceflinger.Transform.Companion.isFlagSet
31 import android.tools.traces.wm.WindowManagerState
32 import android.tools.traces.wm.WindowState
33 import androidx.test.platform.app.InstrumentationRegistry
34 import com.android.wm.shell.Flags
35 
36 object ConditionsFactory {
37 
38     /** Check if this is a phone device instead of a folded foldable. */
39     fun isPhoneNavBar(): Boolean {
40         val isPhone: Boolean
41         if (DeviceStateManagerFlags.deviceStatePropertyMigration()) {
42             val context = InstrumentationRegistry.getInstrumentation().context
43             val deviceStateManager =
44                 context.getSystemService(Context.DEVICE_STATE_SERVICE) as DeviceStateManager?
45             isPhone =
46                 deviceStateManager?.supportedDeviceStates?.any {
47                     it.hasProperty(PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY)
48                 } ?: true
49         } else {
50             val foldedDeviceStatesId: Int =
51                 Resources.getSystem().getIdentifier("config_foldedDeviceStates", "array", "android")
52             isPhone =
53                 if (foldedDeviceStatesId != 0) {
54                     Resources.getSystem().getIntArray(foldedDeviceStatesId).isEmpty()
55                 } else {
56                     true
57                 }
58         }
59         return isPhone
60     }
61 
62     fun getNavBarComponent(wmState: WindowManagerState): IComponentMatcher {
63         var component: IComponentMatcher = ComponentNameMatcher.NAV_BAR
64         if (wmState.isTablet || !isPhoneNavBar() || Flags.enableTaskbarOnPhones()) {
65             component = component.or(ComponentNameMatcher.TASK_BAR)
66         }
67         return component
68     }
69 
70     /**
71      * Condition to check if the [ComponentNameMatcher.NAV_BAR] or [ComponentNameMatcher.TASK_BAR]
72      * windows are visible
73      */
74     fun isNavOrTaskBarVisible(): Condition<DeviceStateDump> =
75         ConditionList(
76             listOf(
77                 isNavOrTaskBarWindowVisible(),
78                 isNavOrTaskBarLayerVisible(),
79                 isNavOrTaskBarLayerOpaque(),
80             )
81         )
82 
83     /**
84      * Condition to check if the [ComponentNameMatcher.NAV_BAR] or [ComponentNameMatcher.TASK_BAR]
85      * windows are visible
86      */
87     fun isNavOrTaskBarWindowVisible(): Condition<DeviceStateDump> =
88         Condition("isNavBarOrTaskBarWindowVisible") {
89             val component = getNavBarComponent(it.wmState)
90             it.wmState.isWindowSurfaceShown(component)
91         }
92 
93     /**
94      * Condition to check if the [ComponentNameMatcher.NAV_BAR] or [ComponentNameMatcher.TASK_BAR]
95      * layers are visible
96      */
97     fun isNavOrTaskBarLayerVisible(): Condition<DeviceStateDump> =
98         Condition("isNavBarOrTaskBarLayerVisible") {
99             val component = getNavBarComponent(it.wmState)
100             it.layerState.isVisible(component)
101         }
102 
103     /** Condition to check if the [ComponentNameMatcher.NAV_BAR] layer is opaque */
104     fun isNavOrTaskBarLayerOpaque(): Condition<DeviceStateDump> =
105         Condition("isNavOrTaskBarLayerOpaque") {
106             val component = getNavBarComponent(it.wmState)
107             it.layerState.getLayerWithBuffer(component)?.color?.alpha() == 1.0f
108         }
109 
110     /** Condition to check if the [ComponentNameMatcher.NAV_BAR] window is visible */
111     fun isNavBarVisible(): Condition<DeviceStateDump> =
112         ConditionList(
113             listOf(isNavBarWindowVisible(), isNavBarLayerVisible(), isNavBarLayerOpaque())
114         )
115 
116     /** Condition to check if the [ComponentNameMatcher.NAV_BAR] window is visible */
117     fun isNavBarWindowVisible(): Condition<DeviceStateDump> =
118         Condition("isNavBarWindowVisible") {
119             it.wmState.isWindowSurfaceShown(ComponentNameMatcher.NAV_BAR)
120         }
121 
122     /** Condition to check if the [ComponentNameMatcher.NAV_BAR] layer is visible */
123     fun isNavBarLayerVisible(): Condition<DeviceStateDump> =
124         isLayerVisible(ComponentNameMatcher.NAV_BAR)
125 
126     /** Condition to check if the [ComponentNameMatcher.NAV_BAR] layer is opaque */
127     fun isNavBarLayerOpaque(): Condition<DeviceStateDump> =
128         Condition("isNavBarLayerOpaque") {
129             it.layerState.getLayerWithBuffer(ComponentNameMatcher.NAV_BAR)?.color?.alpha() == 1.0f
130         }
131 
132     /** Condition to check if the [ComponentNameMatcher.TASK_BAR] window is visible */
133     fun isTaskBarVisible(): Condition<DeviceStateDump> =
134         ConditionList(
135             listOf(isTaskBarWindowVisible(), isTaskBarLayerVisible(), isTaskBarLayerOpaque())
136         )
137 
138     /** Condition to check if the [ComponentNameMatcher.TASK_BAR] window is visible */
139     fun isTaskBarWindowVisible(): Condition<DeviceStateDump> =
140         Condition("isTaskBarWindowVisible") {
141             it.wmState.isWindowSurfaceShown(ComponentNameMatcher.TASK_BAR)
142         }
143 
144     /** Condition to check if the [ComponentNameMatcher.TASK_BAR] layer is visible */
145     fun isTaskBarLayerVisible(): Condition<DeviceStateDump> =
146         isLayerVisible(ComponentNameMatcher.TASK_BAR)
147 
148     /** Condition to check if the [ComponentNameMatcher.TASK_BAR] layer is opaque */
149     fun isTaskBarLayerOpaque(): Condition<DeviceStateDump> =
150         Condition("isTaskBarLayerOpaque") {
151             it.layerState.getLayerWithBuffer(ComponentNameMatcher.TASK_BAR)?.color?.alpha() == 1.0f
152         }
153 
154     /** Condition to check if the [ComponentNameMatcher.STATUS_BAR] window is visible */
155     fun isStatusBarVisible(): Condition<DeviceStateDump> =
156         ConditionList(
157             listOf(isStatusBarWindowVisible(), isStatusBarLayerVisible(), isStatusBarLayerOpaque())
158         )
159 
160     /** Condition to check if the [ComponentNameMatcher.STATUS_BAR] window is visible */
161     fun isStatusBarWindowVisible(): Condition<DeviceStateDump> =
162         Condition("isStatusBarWindowVisible") {
163             it.wmState.isWindowSurfaceShown(ComponentNameMatcher.STATUS_BAR)
164         }
165 
166     /** Condition to check if the [ComponentNameMatcher.STATUS_BAR] layer is visible */
167     fun isStatusBarLayerVisible(): Condition<DeviceStateDump> =
168         isLayerVisible(ComponentNameMatcher.STATUS_BAR)
169 
170     /** Condition to check if the [ComponentNameMatcher.STATUS_BAR] layer is opaque */
171     fun isStatusBarLayerOpaque(): Condition<DeviceStateDump> =
172         Condition("isStatusBarLayerOpaque") {
173             it.layerState.getLayerWithBuffer(ComponentNameMatcher.STATUS_BAR)?.color?.alpha() ==
174                 1.0f
175         }
176 
177     fun isHomeActivityVisible(): Condition<DeviceStateDump> =
178         Condition("isHomeActivityVisible") { it.wmState.isHomeActivityVisible }
179 
180     fun isRecentsActivityVisible(): Condition<DeviceStateDump> =
181         Condition("isRecentsActivityVisible") {
182             it.wmState.isHomeActivityVisible || it.wmState.isRecentsActivityVisible
183         }
184 
185     fun isLauncherLayerVisible(): Condition<DeviceStateDump> =
186         Condition("isLauncherLayerVisible") {
187             it.layerState.isVisible(ComponentNameMatcher.LAUNCHER) ||
188                 it.layerState.isVisible(ComponentNameMatcher.AOSP_LAUNCHER)
189         }
190 
191     /**
192      * Condition to check if WM app transition is idle
193      *
194      * Because in shell transitions, active recents animation is running transition (never idle)
195      * this method always assumed recents are idle
196      */
197     fun isAppTransitionIdle(displayId: Int): Condition<DeviceStateDump> =
198         Condition("isAppTransitionIdle[$displayId]") {
199             (it.wmState.isHomeRecentsComponent && it.wmState.isHomeActivityVisible) ||
200                 it.wmState.isRecentsActivityVisible ||
201                 it.wmState.getDisplay(displayId)?.appTransitionState ==
202                     PlatformConsts.APP_STATE_IDLE
203         }
204 
205     fun containsActivity(componentMatcher: IComponentMatcher): Condition<DeviceStateDump> =
206         Condition("containsActivity[${componentMatcher.toActivityIdentifier()}]") {
207             it.wmState.containsActivity(componentMatcher)
208         }
209 
210     fun containsWindow(componentMatcher: IComponentMatcher): Condition<DeviceStateDump> =
211         Condition("containsWindow[${componentMatcher.toWindowIdentifier()}]") {
212             it.wmState.containsWindow(componentMatcher)
213         }
214 
215     fun isWindowSurfaceShown(componentMatcher: IComponentMatcher): Condition<DeviceStateDump> =
216         Condition("isWindowSurfaceShown[${componentMatcher.toWindowIdentifier()}]") {
217             it.wmState.isWindowSurfaceShown(componentMatcher)
218         }
219 
220     fun isActivityVisible(componentMatcher: IComponentMatcher): Condition<DeviceStateDump> =
221         Condition("isActivityVisible[${componentMatcher.toActivityIdentifier()}]") {
222             it.wmState.isActivityVisible(componentMatcher)
223         }
224 
225     fun isWMStateComplete(): Condition<DeviceStateDump> =
226         Condition("isWMStateComplete") { it.wmState.isComplete() }
227 
228     fun hasRotation(expectedRotation: Rotation, displayId: Int): Condition<DeviceStateDump> {
229         val hasRotationCondition =
230             Condition<DeviceStateDump>("hasRotation[$expectedRotation, display=$displayId]") {
231                 val currRotation = it.wmState.getRotation(displayId)
232                 currRotation == expectedRotation
233             }
234         return ConditionList(
235             listOf(
236                 hasRotationCondition,
237                 isLayerVisible(ComponentNameMatcher.ROTATION).negate(),
238                 isLayerVisible(ComponentNameMatcher.BACK_SURFACE).negate(),
239                 hasLayersAnimating().negate(),
240             )
241         )
242     }
243 
244     fun isWindowVisible(
245         componentMatcher: IComponentMatcher,
246         displayId: Int = 0,
247     ): Condition<DeviceStateDump> =
248         ConditionList(
249             containsActivity(componentMatcher),
250             containsWindow(componentMatcher),
251             isActivityVisible(componentMatcher),
252             isWindowSurfaceShown(componentMatcher),
253             isAppTransitionIdle(displayId),
254         )
255 
256     fun isLayerVisible(componentMatcher: IComponentMatcher): Condition<DeviceStateDump> =
257         Condition("isLayerVisible[${componentMatcher.toLayerIdentifier()}]") {
258             it.layerState.isVisible(componentMatcher)
259         }
260 
261     fun isLayerVisible(layerId: Int): Condition<DeviceStateDump> =
262         Condition("isLayerVisible[layerId=$layerId]") {
263             it.layerState.getLayerById(layerId)?.isVisible ?: false
264         }
265 
266     /** Condition to check if the given layer is opaque */
267     fun isLayerOpaque(componentMatcher: IComponentMatcher): Condition<DeviceStateDump> =
268         Condition("isLayerOpaque[${componentMatcher.toLayerIdentifier()}]") {
269             it.layerState.getLayerWithBuffer(componentMatcher)?.color?.alpha() == 1.0f
270         }
271 
272     fun isLayerColorAlphaOne(componentMatcher: IComponentMatcher): Condition<DeviceStateDump> =
273         Condition("isLayerColorAlphaOne[${componentMatcher.toLayerIdentifier()}]") {
274             it.layerState.visibleLayers
275                 .filter { layer -> componentMatcher.layerMatchesAnyOf(layer) }
276                 .any { layer -> layer.color.alpha() == 1.0f }
277         }
278 
279     fun isLayerColorAlphaOne(layerId: Int): Condition<DeviceStateDump> =
280         Condition("isLayerColorAlphaOne[$layerId]") {
281             val layer = it.layerState.getLayerById(layerId)
282             layer?.color?.alpha() == 1.0f
283         }
284 
285     fun isLayerTransformFlagSet(
286         componentMatcher: IComponentMatcher,
287         transform: Int,
288     ): Condition<DeviceStateDump> =
289         Condition(
290             "isLayerTransformFlagSet[" +
291                 "${componentMatcher.toLayerIdentifier()}," +
292                 "transform=$transform]"
293         ) {
294             it.layerState.visibleLayers
295                 .filter { layer -> componentMatcher.layerMatchesAnyOf(layer) }
296                 .any { layer -> isTransformFlagSet(layer, transform) }
297         }
298 
299     fun isLayerTransformFlagSet(layerId: Int, transform: Int): Condition<DeviceStateDump> =
300         Condition("isLayerTransformFlagSet[$layerId, $transform]") {
301             val layer = it.layerState.getLayerById(layerId)
302             layer?.transform?.type?.isFlagSet(transform) ?: false
303         }
304 
305     fun isLayerTransformIdentity(layerId: Int): Condition<DeviceStateDump> =
306         ConditionList(
307             listOf(
308                 isLayerTransformFlagSet(layerId, Transform.SCALE_VAL).negate(),
309                 isLayerTransformFlagSet(layerId, Transform.TRANSLATE_VAL).negate(),
310                 isLayerTransformFlagSet(layerId, Transform.ROTATE_VAL).negate(),
311             )
312         )
313 
314     private fun isTransformFlagSet(layer: Layer, transform: Int): Boolean =
315         layer.transform.type?.isFlagSet(transform) ?: false
316 
317     fun hasLayersAnimating(): Condition<DeviceStateDump> {
318         var prevState: DeviceStateDump? = null
319         return ConditionList(
320             Condition("hasLayersAnimating") {
321                 val result = it.layerState.isAnimating(prevState?.layerState)
322                 prevState = it
323                 result
324             },
325             isLayerVisible(ComponentNameMatcher.SNAPSHOT).negate(),
326             isLayerVisible(ComponentNameMatcher.SPLASH_SCREEN).negate(),
327         )
328     }
329 
330     fun isPipWindowLayerSizeMatch(layerId: Int): Condition<DeviceStateDump> =
331         Condition("isPipWindowLayerSizeMatch[layerId=$layerId]") {
332             val pipWindow =
333                 it.wmState.pinnedWindows.firstOrNull { pinnedWindow ->
334                     pinnedWindow.layerId == layerId
335                 } ?: error("Unable to find window with layerId $layerId")
336             val windowHeight = pipWindow.frame.height().toFloat()
337             val windowWidth = pipWindow.frame.width().toFloat()
338 
339             val pipLayer = it.layerState.getLayerById(layerId)
340             val layerHeight =
341                 pipLayer?.screenBounds?.height() ?: error("Unable to find layer with id $layerId")
342             val layerWidth = pipLayer.screenBounds.width()
343 
344             windowHeight == layerHeight && windowWidth == layerWidth
345         }
346 
347     fun hasPipWindow(): Condition<DeviceStateDump> =
348         Condition("hasPipWindow") { it.wmState.hasPipWindow() }
349 
350     fun isImeShown(displayId: Int): Condition<DeviceStateDump> =
351         ConditionList(
352             listOf(
353                 isImeOnDisplay(displayId),
354                 isLayerVisible(ComponentNameMatcher.IME),
355                 isLayerOpaque(ComponentNameMatcher.IME),
356                 isImeSurfaceShown(),
357                 isWindowSurfaceShown(ComponentNameMatcher.IME),
358             )
359         )
360 
361     private fun isImeOnDisplay(displayId: Int): Condition<DeviceStateDump> =
362         Condition("isImeOnDisplay[$displayId]") {
363             it.wmState.inputMethodWindowState?.displayId == displayId
364         }
365 
366     private fun isImeSurfaceShown(): Condition<DeviceStateDump> =
367         Condition("isImeSurfaceShown") {
368             it.wmState.inputMethodWindowState?.isSurfaceShown == true &&
369                 it.wmState.inputMethodWindowState?.isVisible == true
370         }
371 
372     fun isAppLaunchEnded(taskId: Int): Condition<DeviceStateDump> =
373         Condition("containsVisibleAppLaunchWindow[taskId=$taskId]") { dump ->
374             val windowStates =
375                 dump.wmState.getRootTask(taskId)?.activities?.flatMap {
376                     it.children.filterIsInstance<WindowState>()
377                 }
378             windowStates != null &&
379                 windowStates.none {
380                     it.attributes.type == PlatformConsts.TYPE_APPLICATION_STARTING && it.isVisible
381                 }
382         }
383 }
384