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