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