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