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.parsers
18 
19 import android.app.ActivityTaskManager
20 import android.app.Instrumentation
21 import android.app.WindowConfiguration
22 import android.graphics.Rect
23 import android.graphics.Region
24 import android.os.SystemClock
25 import android.os.Trace
26 import android.tools.Rotation
27 import android.tools.traces.Condition
28 import android.tools.traces.ConditionsFactory
29 import android.tools.traces.DeviceStateDump
30 import android.tools.traces.LOG_TAG
31 import android.tools.traces.WaitCondition
32 import android.tools.traces.component.ComponentNameMatcher
33 import android.tools.traces.component.ComponentNameMatcher.Companion.IME
34 import android.tools.traces.component.ComponentNameMatcher.Companion.LAUNCHER
35 import android.tools.traces.component.ComponentNameMatcher.Companion.SNAPSHOT
36 import android.tools.traces.component.ComponentNameMatcher.Companion.SPLASH_SCREEN
37 import android.tools.traces.component.ComponentNameMatcher.Companion.SPLIT_DIVIDER
38 import android.tools.traces.component.ComponentNameMatcher.Companion.TRANSITION_SNAPSHOT
39 import android.tools.traces.component.IComponentMatcher
40 import android.tools.traces.getCurrentStateDump
41 import android.tools.traces.surfaceflinger.LayerTraceEntry
42 import android.tools.traces.surfaceflinger.LayersTrace
43 import android.tools.traces.wm.Activity
44 import android.tools.traces.wm.WindowManagerState
45 import android.tools.traces.wm.WindowManagerTrace
46 import android.tools.traces.wm.WindowState
47 import android.util.Log
48 import android.view.Display
49 import androidx.test.platform.app.InstrumentationRegistry
50 import java.util.function.Predicate
51 import java.util.function.Supplier
52 
53 /** Helper class to wait on [WindowManagerState] or [LayerTraceEntry] conditions */
54 open class WindowManagerStateHelper
55 @JvmOverloads
56 constructor(
57     /** Instrumentation to run the tests */
58     private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation(),
59     private val clearCacheAfterParsing: Boolean = true,
60     /** Predicate to supply a new UI information */
61     private val deviceDumpSupplier: Supplier<DeviceStateDump> = Supplier {
62         getCurrentStateDump(clearCacheAfterParsing = clearCacheAfterParsing)
63     },
64     /** Number of attempts to satisfy a wait condition */
65     private val numRetries: Int = DEFAULT_RETRY_LIMIT,
66     /** Interval between wait for state dumps during wait conditions */
67     private val retryIntervalMs: Long = DEFAULT_RETRY_INTERVAL_MS,
68 ) {
69     private var internalState: DeviceStateDump? = null
70 
71     /** Queries the supplier for a new device state */
72     val currentState: DeviceStateDump
73         get() {
74             if (internalState == null) {
75                 internalState = deviceDumpSupplier.get()
76             } else {
77                 StateSyncBuilder().withValidState().waitFor()
78             }
79             return internalState ?: error("Unable to fetch an internal state")
80         }
81 
updateCurrStatenull82     protected open fun updateCurrState(value: DeviceStateDump) {
83         internalState = value
84     }
85 
86     /**
87      * @param componentMatcher Components to search
88      * @return a [WindowState] from the current device state matching [componentMatcher], or null
89      *   otherwise
90      */
getWindownull91     fun getWindow(componentMatcher: IComponentMatcher): WindowState? {
92         return this.currentState.wmState.windowStates.firstOrNull {
93             componentMatcher.windowMatchesAnyOf(it)
94         }
95     }
96 
97     /**
98      * @param componentMatcher Components to search
99      * @return The frame [Region] a [WindowState] matching [componentMatcher]
100      */
getWindowRegionnull101     fun getWindowRegion(componentMatcher: IComponentMatcher): Region =
102         getWindow(componentMatcher)?.frameRegion ?: Region()
103 
104     /** Factory function to create a new [StateSyncBuilder] object from a state helper */
105     fun StateSyncBuilder(): StateSyncBuilder = StateSyncBuilder(deviceDumpSupplier)
106 
107     /**
108      * Class to build conditions for waiting on specific [WindowManagerTrace] and [LayersTrace]
109      * conditions
110      */
111     inner class StateSyncBuilder(private val deviceDumpSupplier: Supplier<DeviceStateDump>) {
112         private val conditionBuilder = createConditionBuilder()
113         private var lastMessage = ""
114 
115         private fun createConditionBuilder(): WaitCondition.Builder<DeviceStateDump> =
116             WaitCondition.Builder(numRetries) { deviceDumpSupplier.get() }
117                 .onStart { Trace.beginSection(it) }
118                 .onEnd { Trace.endSection() }
119                 .onSuccess { updateCurrState(it) }
120                 .onFailure { updateCurrState(it) }
121                 .onLog { msg, isError ->
122                     lastMessage = msg
123                     if (isError) {
124                         Log.e(LOG_TAG, msg)
125                     } else {
126                         Log.d(LOG_TAG, msg)
127                     }
128                 }
129                 .onRetry { SystemClock.sleep(retryIntervalMs) }
130 
131         /**
132          * Adds a new [condition] to the list
133          *
134          * @param condition to wait for
135          */
136         fun add(condition: Condition<DeviceStateDump>): StateSyncBuilder = apply {
137             conditionBuilder.withCondition(condition)
138         }
139 
140         /**
141          * Adds a new [condition] to the list
142          *
143          * @param message describing the condition
144          * @param condition to wait for
145          */
146         @JvmOverloads
147         fun add(message: String = "", condition: Predicate<DeviceStateDump>): StateSyncBuilder =
148             add(Condition(message, condition))
149 
150         /**
151          * Waits until the list of conditions added to [conditionBuilder] are satisfied
152          *
153          * @return if the device state passed all conditions or not
154          */
155         fun waitFor(): Boolean {
156             val passed = conditionBuilder.build().waitFor()
157             // Ensure WindowManagerService wait until all animations have completed
158             instrumentation.waitForIdleSync()
159             instrumentation.uiAutomation.syncInputTransactions()
160             return passed
161         }
162 
163         /**
164          * Waits until the list of conditions added to [conditionBuilder] are satisfied and verifies
165          * the device state passes all conditions
166          *
167          * @throws IllegalArgumentException if the conditions were not met
168          */
169         fun waitForAndVerify() {
170             val success = waitFor()
171             require(success) {
172                 buildString {
173                     appendLine(lastMessage)
174 
175                     val wmState = internalState?.wmState
176                     val layerState = internalState?.layerState
177 
178                     if (wmState != null) {
179                         appendLine("Last checked WM state at ${wmState.timestamp}.")
180                     }
181                     if (layerState != null) {
182                         appendLine("Last checked layer state at ${layerState.timestamp}.")
183                     }
184                 }
185             }
186         }
187 
188         /**
189          * Waits for an app matching [componentMatcher] to be visible, in full screen, and for
190          * nothing to be animating
191          *
192          * @param componentMatcher Components to search
193          * @param displayId of the target display
194          */
195         @JvmOverloads
196         fun withFullScreenApp(
197             componentMatcher: IComponentMatcher,
198             displayId: Int = Display.DEFAULT_DISPLAY,
199         ) =
200             withFullScreenAppCondition(componentMatcher)
201                 .withAppTransitionIdle(displayId)
202                 .add(ConditionsFactory.isLayerVisible(componentMatcher))
203 
204         /**
205          * Waits for an app matching [componentMatcher] to be visible, in freeform, and for nothing
206          * to be animating
207          *
208          * @param componentMatcher Components to search
209          * @param displayId of the target display
210          */
211         @JvmOverloads
212         fun withFreeformApp(
213             componentMatcher: IComponentMatcher,
214             displayId: Int = Display.DEFAULT_DISPLAY,
215         ) =
216             withFreeformAppCondition(componentMatcher)
217                 .withAppTransitionIdle(displayId)
218                 .add(ConditionsFactory.isLayerVisible(componentMatcher))
219 
220         /**
221          * Waits until the home activity is visible and nothing to be animating
222          *
223          * @param displayId of the target display
224          */
225         @JvmOverloads
226         fun withHomeActivityVisible(displayId: Int = Display.DEFAULT_DISPLAY) =
227             withAppTransitionIdle(displayId)
228                 .withNavOrTaskBarVisible()
229                 .withStatusBarVisible()
230                 .add(ConditionsFactory.isHomeActivityVisible())
231                 .add(ConditionsFactory.isLauncherLayerVisible())
232 
233         /**
234          * Waits until the split-screen divider is visible and nothing to be animating
235          *
236          * @param displayId of the target display
237          */
238         @JvmOverloads
239         fun withSplitDividerVisible(displayId: Int = Display.DEFAULT_DISPLAY) =
240             withAppTransitionIdle(displayId).add(ConditionsFactory.isLayerVisible(SPLIT_DIVIDER))
241 
242         /**
243          * Waits until the home activity is visible and nothing to be animating
244          *
245          * @param displayId of the target display
246          */
247         @JvmOverloads
248         fun withRecentsActivityVisible(displayId: Int = Display.DEFAULT_DISPLAY) =
249             withAppTransitionIdle(displayId)
250                 .add(ConditionsFactory.isRecentsActivityVisible())
251                 .add(ConditionsFactory.isLayerVisible(LAUNCHER))
252 
253         /**
254          * Wait for specific rotation for the display with id [displayId]
255          *
256          * @param rotation expected. Values are [Surface#Rotation]
257          * @param displayId of the target display
258          */
259         @JvmOverloads
260         fun withRotation(rotation: Rotation, displayId: Int = Display.DEFAULT_DISPLAY) =
261             withAppTransitionIdle(displayId).add(ConditionsFactory.hasRotation(rotation, displayId))
262 
263         /**
264          * Waits until a [WindowState] matching [componentMatcher] has a state of [activityState]
265          *
266          * @param componentMatcher Components to search
267          * @param activityStates expected activity states
268          */
269         fun withActivityState(componentMatcher: IComponentMatcher, vararg activityStates: String) =
270             add(
271                 Condition(
272                     "state of ${componentMatcher.toActivityIdentifier()} to be any of " +
273                         activityStates.joinToString()
274                 ) {
275                     activityStates.any { state ->
276                         it.wmState.hasActivityState(componentMatcher, state)
277                     }
278                 }
279             )
280 
281         /**
282          * Waits until the [ComponentNameMatcher.NAV_BAR] or [ComponentNameMatcher.TASK_BAR] are
283          * visible (windows and layers)
284          */
285         fun withNavOrTaskBarVisible() = add(ConditionsFactory.isNavOrTaskBarVisible())
286 
287         /** Waits until the navigation and status bars are visible (windows and layers) */
288         fun withStatusBarVisible() = add(ConditionsFactory.isStatusBarVisible())
289 
290         /**
291          * Wait until neither an [Activity] nor a [WindowState] matching [componentMatcher] exist on
292          * the display with id [displayId] and for nothing to be animating
293          *
294          * @param componentMatcher Components to search
295          * @param displayId of the target display
296          */
297         @JvmOverloads
298         fun withActivityRemoved(
299             componentMatcher: IComponentMatcher,
300             displayId: Int = Display.DEFAULT_DISPLAY,
301         ) =
302             withAppTransitionIdle(displayId)
303                 .add(ConditionsFactory.containsActivity(componentMatcher).negate())
304                 .add(ConditionsFactory.containsWindow(componentMatcher).negate())
305 
306         /**
307          * Wait until the splash screen and snapshot starting windows no longer exist, no layers are
308          * animating, and [WindowManagerState] is idle on display [displayId]
309          *
310          * @param displayId of the target display
311          */
312         @JvmOverloads
313         fun withAppTransitionIdle(displayId: Int = Display.DEFAULT_DISPLAY) =
314             withSplashScreenGone()
315                 .withSnapshotGone()
316                 .add(ConditionsFactory.isAppTransitionIdle(displayId))
317                 .add(ConditionsFactory.hasLayersAnimating().negate())
318 
319         /**
320          * Wait until least one [WindowState] matching [componentMatcher] is not visible on display
321          * with idd [displayId] and nothing is animating
322          *
323          * @param componentMatcher Components to search
324          * @param displayId of the target display
325          */
326         @JvmOverloads
327         fun withWindowSurfaceDisappeared(
328             componentMatcher: IComponentMatcher,
329             displayId: Int = Display.DEFAULT_DISPLAY,
330         ) =
331             withAppTransitionIdle(displayId)
332                 .add(ConditionsFactory.isWindowSurfaceShown(componentMatcher).negate())
333                 .add(ConditionsFactory.isLayerVisible(componentMatcher).negate())
334                 .add(ConditionsFactory.isAppTransitionIdle(displayId))
335 
336         /**
337          * Wait until least one [WindowState] matching [componentMatcher] is visible on display with
338          * idd [displayId] and nothing is animating
339          *
340          * @param componentMatcher Components to search
341          * @param displayId of the target display
342          */
343         @JvmOverloads
344         fun withWindowSurfaceAppeared(
345             componentMatcher: IComponentMatcher,
346             displayId: Int = Display.DEFAULT_DISPLAY,
347         ) =
348             withAppTransitionIdle(displayId)
349                 .add(ConditionsFactory.isWindowSurfaceShown(componentMatcher))
350                 .add(ConditionsFactory.isLayerVisible(componentMatcher))
351 
352         /**
353          * Wait until least one [LayerState] matching [componentMatcher] is visible
354          *
355          * @param componentMatcher Components to search
356          */
357         fun withLayerVisible(componentMatcher: IComponentMatcher) =
358             add(ConditionsFactory.isLayerVisible(componentMatcher))
359 
360         /**
361          * Wait until least one layer matching [componentMatcher] has [expectedRegion]
362          *
363          * @param componentMatcher Components to search
364          * @param expectedRegion of the target surface
365          * @param compareFn custom comparator to compare `visibleRegion` vs `expectedRegion`
366          */
367         fun withSurfaceMatchingVisibleRegion(
368             componentMatcher: IComponentMatcher,
369             expectedRegion: Region,
370             compareFn: (Region, Region) -> Boolean = { surfaceRegion, expected ->
371                 surfaceRegion == expected
372             },
373         ) = add(Condition("surfaceRegion") {
374             val layer = it.layerState.visibleLayers.firstOrNull { layer ->
375                 componentMatcher.layerMatchesAnyOf(layer)
376             }
377             layer?.let {
378                 // TODO(pablogamito): Remove non-null assertion once visibleRegion in
379                 // LayerProperties is no longer nullable.
380                 compareFn(layer.visibleRegion!!, expectedRegion)
381             } ?: false
382         })
383 
384         /**
385          * Waits until the IME window and layer are visible
386          *
387          * @param displayId of the target display
388          */
389         @JvmOverloads
390         fun withImeShown(displayId: Int = Display.DEFAULT_DISPLAY) =
391             withAppTransitionIdle(displayId).add(ConditionsFactory.isImeShown(displayId))
392 
393         /**
394          * Waits until the [IME] layer is no longer visible.
395          *
396          * Cannot wait for the window as its visibility information is updated at a later state and
397          * is not reliable in the trace
398          *
399          * @param displayId of the target display
400          */
401         @JvmOverloads
402         fun withImeGone(displayId: Int = Display.DEFAULT_DISPLAY) =
403             withAppTransitionIdle(displayId)
404                 .add(ConditionsFactory.isLayerVisible(IME).negate())
405                 .add(ConditionsFactory.isImeShown(displayId).negate())
406 
407         /**
408          * Waits until a window is in PIP mode. That is:
409          * - wait until a window is pinned ([WindowManagerState.pinnedWindows])
410          * - no layers animating
411          * - and [ComponentNameMatcher.PIP_CONTENT_OVERLAY] is no longer visible
412          *
413          * @param displayId of the target display
414          */
415         @JvmOverloads
416         fun withPipShown(displayId: Int = Display.DEFAULT_DISPLAY) =
417             withAppTransitionIdle(displayId).add(ConditionsFactory.hasPipWindow())
418 
419         /**
420          * Waits until a window is no longer in PIP mode. That is:
421          * - wait until there are no pinned ([WindowManagerState.pinnedWindows])
422          * - no layers animating
423          * - and [ComponentNameMatcher.PIP_CONTENT_OVERLAY] is no longer visible
424          *
425          * @param displayId of the target display
426          */
427         @JvmOverloads
428         fun withPipGone(displayId: Int = Display.DEFAULT_DISPLAY) =
429             withAppTransitionIdle(displayId).add(ConditionsFactory.hasPipWindow().negate())
430 
431         /** Waits until the [SNAPSHOT] is gone */
432         fun withSnapshotGone() = add(ConditionsFactory.isLayerVisible(SNAPSHOT).negate())
433 
434         /** Waits until the [SPLASH_SCREEN] is gone */
435         fun withSplashScreenGone() = add(ConditionsFactory.isLayerVisible(SPLASH_SCREEN).negate())
436 
437         /** Waits until the [TRANSITION_SNAPSHOT] is gone */
438         fun withTransitionSnapshotGone() =
439             add(ConditionsFactory.isLayerVisible(TRANSITION_SNAPSHOT).negate())
440 
441         /** Waits until the is no top visible app window in the [WindowManagerState] */
442         fun withoutTopVisibleAppWindows() =
443             add("noAppWindowsOnTop") { it.wmState.topVisibleAppWindow == null }
444 
445         /** Waits until the keyguard is showing */
446         fun withKeyguardShowing() = add("withKeyguardShowing") { it.wmState.isKeyguardShowing }
447 
448         /** Waits until the given app is the top visible app window. */
449         fun withTopVisibleApp(componentMatcher: IComponentMatcher): StateSyncBuilder {
450             return add("withTopVisibleApp") {
451                 val topVisible = it.wmState.topVisibleAppWindow
452                 return@add topVisible != null && componentMatcher.windowMatchesAnyOf(topVisible)
453             }
454         }
455 
456         /**
457          * Wait for the activities to appear in proper stacks and for valid state in AM and WM.
458          *
459          * @param waitForActivityState array of activity states to wait for.
460          */
461         internal fun withValidState(vararg waitForActivityState: WaitForValidActivityState) =
462             waitForValidStateCondition(*waitForActivityState)
463 
464         private fun waitForValidStateCondition(vararg waitForCondition: WaitForValidActivityState) =
465             apply {
466                 add(ConditionsFactory.isWMStateComplete())
467                 if (waitForCondition.isNotEmpty()) {
468                     add(
469                         Condition(
470                             "!shouldWaitForActivityState(${waitForCondition.joinToString()})"
471                         ) {
472                             !shouldWaitForActivities(it, *waitForCondition)
473                         }
474                     )
475                 }
476             }
477 
478         fun withFullScreenAppCondition(componentMatcher: IComponentMatcher) =
479             waitForValidStateCondition(
480                 WaitForValidActivityState.Builder(componentMatcher)
481                     .setWindowingMode(WindowConfiguration.WINDOWING_MODE_FULLSCREEN)
482                     .setActivityType(WindowConfiguration.ACTIVITY_TYPE_STANDARD)
483                     .build()
484             )
485 
486         fun withFreeformAppCondition(componentMatcher: IComponentMatcher) =
487             waitForValidStateCondition(
488                 WaitForValidActivityState.Builder(componentMatcher)
489                     .setWindowingMode(WindowConfiguration.WINDOWING_MODE_FREEFORM)
490                     .setActivityType(WindowConfiguration.ACTIVITY_TYPE_STANDARD)
491                     .build()
492             )
493     }
494 
495     companion object {
496         // TODO(b/112837428): Implement a incremental retry policy to reduce the unnecessary
497         // constant time, currently keep the default as 5*1s because most of the original code
498         // uses it, and some tests might be sensitive to the waiting interval.
499         private const val DEFAULT_RETRY_LIMIT = 20
500         private const val DEFAULT_RETRY_INTERVAL_MS = 300L
501 
502         /** @return true if it should wait for some activities to become visible. */
shouldWaitForActivitiesnull503         private fun shouldWaitForActivities(
504             state: DeviceStateDump,
505             vararg waitForActivitiesVisible: WaitForValidActivityState,
506         ): Boolean {
507             if (waitForActivitiesVisible.isEmpty()) {
508                 return false
509             }
510             // If the caller is interested in waiting for some particular activity windows to be
511             // visible before compute the state. Check for the visibility of those activity windows
512             // and for placing them in correct stacks (if requested).
513             var allActivityWindowsVisible = true
514             var tasksInCorrectStacks = true
515             for (activityState in waitForActivitiesVisible) {
516                 val matchingWindowStates =
517                     state.wmState.getMatchingVisibleWindowState(
518                         activityState.activityMatcher
519                             ?: error("Activity name missing in $activityState")
520                     )
521                 val activityWindowVisible = matchingWindowStates.isNotEmpty()
522 
523                 if (!activityWindowVisible) {
524                     Log.i(LOG_TAG, "Activity window not visible: ${activityState.windowIdentifier}")
525                     allActivityWindowsVisible = false
526                 } else if (!state.wmState.isActivityVisible(activityState.activityMatcher)) {
527                     Log.i(LOG_TAG, "Activity not visible: ${activityState.activityMatcher}")
528                     allActivityWindowsVisible = false
529                 } else {
530                     // Check if window is already the correct state requested by test.
531                     var windowInCorrectState = false
532                     for (ws in matchingWindowStates) {
533                         if (
534                             activityState.stackId != ActivityTaskManager.INVALID_STACK_ID &&
535                                 ws.stackId != activityState.stackId
536                         ) {
537                             continue
538                         }
539                         if (!ws.isWindowingModeCompatible(activityState.windowingMode)) {
540                             continue
541                         }
542                         if (
543                             activityState.activityType !=
544                                 WindowConfiguration.ACTIVITY_TYPE_UNDEFINED &&
545                                 ws.activityType != activityState.activityType
546                         ) {
547                             continue
548                         }
549                         windowInCorrectState = true
550                         break
551                     }
552                     if (!windowInCorrectState) {
553                         Log.i(LOG_TAG, "Window in incorrect stack: $activityState")
554                         tasksInCorrectStacks = false
555                     }
556                 }
557             }
558             return !allActivityWindowsVisible || !tasksInCorrectStacks
559         }
560     }
561 }
562