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