• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2020 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 com.android.server.wm.traces.parser.windowmanager
18 
19 import android.app.ActivityTaskManager
20 import android.app.Instrumentation
21 import android.app.WindowConfiguration
22 import android.graphics.Rect
23 import android.os.SystemClock
24 import android.util.Log
25 import android.view.Display
26 import androidx.test.platform.app.InstrumentationRegistry
27 import com.android.server.wm.traces.common.windowmanager.windows.ConfigurationContainer
28 import com.android.server.wm.traces.common.windowmanager.windows.WindowContainer
29 import com.android.server.wm.traces.common.windowmanager.windows.WindowState
30 import com.android.server.wm.traces.common.Condition
31 import com.android.server.wm.traces.common.ConditionList
32 import com.android.server.wm.traces.common.DeviceStateDump
33 import com.android.server.wm.traces.common.FlickerComponentName
34 import com.android.server.wm.traces.common.FlickerComponentName.Companion.IME
35 import com.android.server.wm.traces.common.FlickerComponentName.Companion.SNAPSHOT
36 import com.android.server.wm.traces.parser.LOG_TAG
37 import com.android.server.wm.traces.common.WaitCondition
38 import com.android.server.wm.traces.common.layers.BaseLayerTraceEntry
39 import com.android.server.wm.traces.common.WindowManagerConditionsFactory
40 import com.android.server.wm.traces.common.region.Region
41 import com.android.server.wm.traces.common.windowmanager.WindowManagerState
42 import com.android.server.wm.traces.parser.getCurrentStateDump
43 
44 open class WindowManagerStateHelper @JvmOverloads constructor(
45     /**
46      * Instrumentation to run the tests
47      */
48     private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation(),
49     /**
50      * Predicate to supply a new UI information
51      */
52     private val deviceDumpSupplier:
53         () -> DeviceStateDump<WindowManagerState, BaseLayerTraceEntry> =
54             {
55             val currState = getCurrentStateDump(instrumentation.uiAutomation)
56             DeviceStateDump(
57                 currState.wmState ?: error("Unable to parse WM trace"),
58                 currState.layerState ?: error("Unable to parse Layers trace")
59             )
60         },
61     /**
62      * Number of attempts to satisfy a wait condition
63      */
64     private val numRetries: Int = WaitCondition.DEFAULT_RETRY_LIMIT,
65     /**
66      * Interval between wait for state dumps during wait conditions
67      */
68     private val retryIntervalMs: Long = WaitCondition.DEFAULT_RETRY_INTERVAL_MS
69 ) {
70     private var internalState: DeviceStateDump<WindowManagerState, BaseLayerTraceEntry>? = null
71 
72     /**
73      * Queries the supplier for a new device state
74      */
75     val currentState: DeviceStateDump<WindowManagerState, BaseLayerTraceEntry>
76         get() {
77             if (internalState == null) {
78                 internalState = deviceDumpSupplier.invoke()
79             } else {
80                 waitForValidState()
81             }
82             return internalState ?: error("Unable to fetch an internal state")
83         }
84 
updateCurrStatenull85     protected open fun updateCurrState(
86         value: DeviceStateDump<WindowManagerState, BaseLayerTraceEntry>
87     ) {
88         internalState = value
89     }
90 
createConditionBuildernull91     private fun createConditionBuilder():
92         WaitCondition.Builder<DeviceStateDump<WindowManagerState, BaseLayerTraceEntry>> =
93         WaitCondition.Builder(deviceDumpSupplier, numRetries)
94             .onSuccess { updateCurrState(it) }
<lambda>null95             .onFailure { updateCurrState(it) }
msgnull96             .onLog { msg, isError -> if (isError) Log.e(LOG_TAG, msg) else Log.d(LOG_TAG, msg) }
<lambda>null97             .onRetry { SystemClock.sleep(retryIntervalMs) }
98 
99     /**
100      * Wait for the activities to appear in proper stacks and for valid state in AM and WM.
101      * @param waitForActivityState array of activity states to wait for.
102      */
waitForValidStatenull103     private fun waitForValidState(vararg waitForActivityState: WaitForValidActivityState) =
104         waitFor(waitForValidStateCondition(*waitForActivityState))
105 
106     fun waitForFullScreenApp(component: FlickerComponentName) =
107         require(
108         waitFor(isAppFullScreen(component), snapshotGoneCondition)) {
109         "Expected ${component.toWindowName()} to be in full screen"
110     }
111 
<lambda>null112     fun waitForHomeActivityVisible() = require(waitFor(isHomeActivityVisible)) {
113         "Expected home activity to be visible"
114     }
115 
waitForRecentsActivityVisiblenull116     fun waitForRecentsActivityVisible() = require(
117         waitFor("isRecentsActivityVisible") { it.wmState.isRecentsActivityVisible }) {
118         "Expected recents activity to be visible"
119     }
120 
121     /**
122      * Wait for specific rotation for the default display. Values are Surface#Rotation
123      */
124     @JvmOverloads
waitForRotationnull125     fun waitForRotation(rotation: Int, displayId: Int = Display.DEFAULT_DISPLAY) {
126         val hasRotationCondition = WindowManagerConditionsFactory.hasRotation(rotation, displayId)
127         val result = waitFor(
128             WindowManagerConditionsFactory.isAppTransitionIdle(Display.DEFAULT_DISPLAY),
129             Condition("waitForRotation[$rotation]") {
130                 if (!it.wmState.canRotate) {
131                     Log.v(LOG_TAG, "Rotation is not allowed in the state")
132                     true
133                 } else {
134                     hasRotationCondition.isSatisfied(it)
135                 }
136             })
137         require(result) { "Could not change rotation" }
138     }
139 
waitForActivityStatenull140     fun waitForActivityState(activity: FlickerComponentName, activityState: String): Boolean {
141         val activityName = activity.toActivityName()
142         return waitFor("state of $activityName to be $activityState") {
143             it.wmState.hasActivityState(activityName, activityState)
144         }
145     }
146 
147     /**
148      * Waits until the navigation and status bars are visible (windows and layers)
149      */
waitForNavBarStatusBarVisiblenull150     fun waitForNavBarStatusBarVisible(): Boolean =
151         waitFor(
152             WindowManagerConditionsFactory.isNavBarVisible(),
153             WindowManagerConditionsFactory.isStatusBarVisible())
154 
155     fun waitForVisibleWindow(component: FlickerComponentName) = require(
156         waitFor(
157             WindowManagerConditionsFactory.isWindowVisible(component),
158             WindowManagerConditionsFactory.isAppTransitionIdle(Display.DEFAULT_DISPLAY))) {
159         "Expected window ${component.toWindowName()} to be visible"
160     }
161 
waitForActivityRemovednull162     fun waitForActivityRemoved(component: FlickerComponentName) = require(
163         waitFor(
164             WindowManagerConditionsFactory.containsActivity(component).negate(),
165             WindowManagerConditionsFactory.containsWindow(component).negate(),
166             WindowManagerConditionsFactory.isAppTransitionIdle(Display.DEFAULT_DISPLAY))) {
167         "Expected activity ${component.toWindowName()} to have been removed"
168     }
169 
170     @JvmOverloads
waitForAppTransitionIdlenull171     fun waitForAppTransitionIdle(displayId: Int = Display.DEFAULT_DISPLAY): Boolean =
172         waitFor(WindowManagerConditionsFactory.isAppTransitionIdle(displayId))
173 
174     fun waitForWindowSurfaceDisappeared(component: FlickerComponentName) = require(
175         waitFor(
176             WindowManagerConditionsFactory.isWindowSurfaceShown(component).negate(),
177             WindowManagerConditionsFactory.isAppTransitionIdle(Display.DEFAULT_DISPLAY))) {
178         "Expected surface ${component.toLayerName()} to disappear"
179     }
180 
waitForSurfaceAppearednull181     fun waitForSurfaceAppeared(component: FlickerComponentName) = require(
182         waitFor(
183             WindowManagerConditionsFactory.isWindowSurfaceShown(component),
184             WindowManagerConditionsFactory.isAppTransitionIdle(Display.DEFAULT_DISPLAY))) {
185         "Expected surface ${component.toLayerName()} to appear"
186     }
187 
waitFornull188     fun waitFor(
189         vararg conditions: Condition<DeviceStateDump<WindowManagerState, BaseLayerTraceEntry>>
190     ): Boolean {
191         val builder = createConditionBuilder()
192         conditions.forEach { builder.withCondition(it) }
193         return builder.build().waitFor()
194     }
195 
196     @JvmOverloads
waitFornull197     fun waitFor(
198         message: String = "",
199         waitCondition: (DeviceStateDump<WindowManagerState, BaseLayerTraceEntry>) -> Boolean
200     ): Boolean = waitFor(Condition(message, waitCondition))
201 
202     /**
203      * Waits until the IME window and layer are visible
204      */
205     fun waitImeShown() = require(waitFor(imeShownCondition)) { "Expected IME to be visible" }
206 
207     /**
208      * Waits until the IME layer is no longer visible. Cannot wait for the window as
209      * its visibility information is updated at a later state and is not reliable in
210      * the trace
211      */
<lambda>null212     fun waitImeGone() = require(waitFor(imeGoneCondition)) { "Expected IME not to be visible" }
213 
214     /**
215      * Waits until a window is in PIP mode. That is:
216      *
217      * - wait until a window is pinned ([WindowManagerState.pinnedWindows])
218      * - no layers animating
219      * - and [FlickerComponentName.PIP_CONTENT_OVERLAY] is no longer visible
220      */
<lambda>null221     fun waitPipShown() = require(waitFor(pipShownCondition)) { "Expected PIP window to be visible" }
222 
223     /**
224      * Waits until a window is no longer in PIP mode. That is:
225      *
226      * - wait until there are no pinned ([WindowManagerState.pinnedWindows])
227      * - no layers animating
228      * - and [FlickerComponentName.PIP_CONTENT_OVERLAY] is no longer visible
229      */
<lambda>null230     fun waitPipGone() = require(waitFor(pipGoneCondition)) { "Expected PIP window to be gone" }
231 
232     /**
233      * Obtains a [WindowContainer] from the current device state, or null if the WindowContainer
234      * doesn't exist
235      */
getWindownull236     fun getWindow(activity: FlickerComponentName): WindowState? {
237         val windowName = activity.toWindowName()
238         return this.currentState.wmState.windowStates
239             .firstOrNull { it.title == windowName }
240     }
241 
242     /**
243      * Obtains the region of a window in the state, or an empty [Rect] is there are none
244      */
getWindowRegionnull245     fun getWindowRegion(activity: FlickerComponentName): Region {
246         val window = getWindow(activity)
247         return window?.frameRegion ?: Region.EMPTY
248     }
249 
250     companion object {
251         @JvmStatic
252         val isHomeActivityVisible = ConditionList(
253             WindowManagerConditionsFactory.isHomeActivityVisible(),
254             WindowManagerConditionsFactory.isAppTransitionIdle(Display.DEFAULT_DISPLAY),
255             WindowManagerConditionsFactory.isNavBarVisible(),
256             WindowManagerConditionsFactory.isStatusBarVisible())
257 
258         @JvmStatic
259         val imeGoneCondition = ConditionList(
260             WindowManagerConditionsFactory.isLayerVisible(IME).negate(),
261             WindowManagerConditionsFactory.isAppTransitionIdle(Display.DEFAULT_DISPLAY))
262 
263         @JvmStatic
264         val imeShownCondition = ConditionList(
265             WindowManagerConditionsFactory.isImeShown(Display.DEFAULT_DISPLAY),
266             WindowManagerConditionsFactory.isAppTransitionIdle(Display.DEFAULT_DISPLAY)
267         )
268 
269         @JvmStatic
270         val snapshotGoneCondition = ConditionList(
271                 WindowManagerConditionsFactory.isLayerVisible(SNAPSHOT).negate(),
272                 WindowManagerConditionsFactory.isAppTransitionIdle(Display.DEFAULT_DISPLAY))
273 
274         @JvmStatic
275         val pipShownCondition = ConditionList(
276             WindowManagerConditionsFactory.hasLayersAnimating().negate(),
277             WindowManagerConditionsFactory.hasPipWindow(),
278             WindowManagerConditionsFactory.isAppTransitionIdle(Display.DEFAULT_DISPLAY))
279 
280         @JvmStatic
281         val pipGoneCondition = ConditionList(
282             WindowManagerConditionsFactory.hasLayersAnimating().negate(),
283             WindowManagerConditionsFactory.hasPipWindow().negate(),
284             WindowManagerConditionsFactory.isAppTransitionIdle(Display.DEFAULT_DISPLAY))
285 
waitForValidStateConditionnull286         fun waitForValidStateCondition(
287             vararg waitForActivitiesVisible: WaitForValidActivityState
288         ): Condition<DeviceStateDump<WindowManagerState, BaseLayerTraceEntry>> {
289             val conditions = mutableListOf(WindowManagerConditionsFactory.isWMStateComplete())
290 
291             if (waitForActivitiesVisible.isNotEmpty()) {
292                 conditions.add(Condition("!shouldWaitForActivities") {
293                     !shouldWaitForActivities(it, *waitForActivitiesVisible)
294                 })
295             }
296 
297             return ConditionList(*conditions.toTypedArray())
298         }
299 
isAppFullScreennull300         fun isAppFullScreen(component: FlickerComponentName) =
301             waitForValidStateCondition(WaitForValidActivityState
302                 .Builder(component)
303                 .setWindowingMode(WindowConfiguration.WINDOWING_MODE_FULLSCREEN)
304                 .setActivityType(WindowConfiguration.ACTIVITY_TYPE_STANDARD)
305                 .build()
306             )
307 
308         /**
309          * @return true if should wait for some activities to become visible.
310          */
311         private fun shouldWaitForActivities(
312             state: DeviceStateDump<WindowManagerState, BaseLayerTraceEntry>,
313             vararg waitForActivitiesVisible: WaitForValidActivityState
314         ): Boolean {
315             if (waitForActivitiesVisible.isEmpty()) {
316                 return false
317             }
318             // If the caller is interested in waiting for some particular activity windows to be
319             // visible before compute the state. Check for the visibility of those activity windows
320             // and for placing them in correct stacks (if requested).
321             var allActivityWindowsVisible = true
322             var tasksInCorrectStacks = true
323             for (activityState in waitForActivitiesVisible) {
324                 val matchingWindowStates = state.wmState.getMatchingVisibleWindowState(
325                     activityState.windowName ?: "")
326                 val activityWindowVisible = matchingWindowStates.isNotEmpty()
327 
328                 if (!activityWindowVisible) {
329                     Log.i(LOG_TAG, "Activity window not visible: ${activityState.windowName}")
330                     allActivityWindowsVisible = false
331                 } else if (activityState.activityName != null &&
332                     !state.wmState.isActivityVisible(activityState.activityName.toActivityName())) {
333                     Log.i(LOG_TAG, "Activity not visible: ${activityState.activityName}")
334                     allActivityWindowsVisible = false
335                 } else {
336                     // Check if window is already the correct state requested by test.
337                     var windowInCorrectState = false
338                     for (ws in matchingWindowStates) {
339                         if (activityState.stackId != ActivityTaskManager.INVALID_STACK_ID &&
340                             ws.stackId != activityState.stackId) {
341                             continue
342                         }
343                         if (!ws.isWindowingModeCompatible(activityState.windowingMode)) {
344                             continue
345                         }
346                         if (activityState.activityType !=
347                                 WindowConfiguration.ACTIVITY_TYPE_UNDEFINED &&
348                             ws.activityType != activityState.activityType) {
349                             continue
350                         }
351                         windowInCorrectState = true
352                         break
353                     }
354                     if (!windowInCorrectState) {
355                         Log.i(LOG_TAG, "Window in incorrect stack: $activityState")
356                         tasksInCorrectStacks = false
357                     }
358                 }
359             }
360             return !allActivityWindowsVisible || !tasksInCorrectStacks
361         }
362 
ConfigurationContainernull363         private fun ConfigurationContainer.isWindowingModeCompatible(
364             requestedWindowingMode: Int
365         ): Boolean {
366             return when (requestedWindowingMode) {
367                 WindowConfiguration.WINDOWING_MODE_UNDEFINED -> true
368                 else -> windowingMode == requestedWindowingMode
369             }
370         }
371     }
372 }
373