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