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