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