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.flicker.subject.wm 18 19 import android.tools.Rotation 20 import android.tools.flicker.subject.FlickerTraceSubject 21 import android.tools.flicker.subject.region.RegionTraceSubject 22 import android.tools.io.Reader 23 import android.tools.traces.component.ComponentNameMatcher 24 import android.tools.traces.component.IComponentMatcher 25 import android.tools.traces.region.RegionTrace 26 import android.tools.traces.wm.WindowManagerTrace 27 import android.tools.traces.wm.WindowState 28 29 /** 30 * Subject for [WindowManagerTrace] objects, used to make assertions over behaviors that occur 31 * throughout a whole trace. 32 * 33 * To make assertions over a trace it is recommended to create a subject using 34 * [WindowManagerTraceSubject](myTrace). 35 * 36 * Example: 37 * ``` 38 * val trace = WindowManagerTraceParser().parse(myTraceFile) 39 * val subject = WindowManagerTraceSubject(trace) 40 * .contains("ValidWindow") 41 * .notContains("ImaginaryWindow") 42 * .showsAboveAppWindow("NavigationBar") 43 * .forAllEntries() 44 * ``` 45 * 46 * Example2: 47 * ``` 48 * val trace = WindowManagerTraceParser().parse(myTraceFile) 49 * val subject = WindowManagerTraceSubject(trace) { 50 * check(myCustomAssertion(this)) { "My assertion lazy message" } 51 * } 52 * ``` 53 */ 54 class WindowManagerTraceSubject( 55 val trace: WindowManagerTrace, 56 override val reader: Reader? = null 57 ) : 58 FlickerTraceSubject<WindowManagerStateSubject>(), 59 IWindowManagerSubject<WindowManagerTraceSubject, RegionTraceSubject> { 60 61 override val subjects by lazy { 62 trace.entries.map { WindowManagerStateSubject(it, reader, this) } 63 } 64 65 /** {@inheritDoc} */ 66 override fun then(): WindowManagerTraceSubject = apply { super.then() } 67 68 /** {@inheritDoc} */ 69 override fun skipUntilFirstAssertion(): WindowManagerTraceSubject = apply { 70 super.skipUntilFirstAssertion() 71 } 72 73 /** {@inheritDoc} */ 74 override fun isEmpty(): WindowManagerTraceSubject = apply { 75 check { "Trace is empty" }.that(trace.entries.isEmpty()).isEqual(true) 76 } 77 78 /** {@inheritDoc} */ 79 override fun isNotEmpty(): WindowManagerTraceSubject = apply { 80 check { "Trace is not empty" }.that(trace.entries.isEmpty()).isEqual(false) 81 } 82 83 /** 84 * @return List of [WindowStateSubject]s matching [componentMatcher] in the order they 85 * 86 * ``` 87 * appear on the trace 88 * 89 * @param componentMatcher 90 * ``` 91 * 92 * Components to search 93 */ 94 fun windowStates(componentMatcher: IComponentMatcher): List<WindowStateSubject> = windowStates { 95 componentMatcher.windowMatchesAnyOf(it) 96 } 97 98 /** 99 * @return List of [WindowStateSubject]s matching [predicate] in the order they 100 * 101 * ``` 102 * appear on the trace 103 * 104 * @param predicate 105 * ``` 106 * 107 * To search 108 */ 109 fun windowStates(predicate: (WindowState) -> Boolean): List<WindowStateSubject> { 110 return subjects.mapNotNull { it.windowState { window -> predicate(window) } } 111 } 112 113 /** {@inheritDoc} */ 114 override fun notContains(componentMatcher: IComponentMatcher): WindowManagerTraceSubject = 115 notContains(componentMatcher, isOptional = false) 116 117 /** See [notContains] */ 118 fun notContains( 119 componentMatcher: IComponentMatcher, 120 isOptional: Boolean 121 ): WindowManagerTraceSubject = apply { 122 addAssertion("notContains(${componentMatcher.toWindowIdentifier()})", isOptional) { 123 it.notContains(componentMatcher) 124 } 125 } 126 127 /** {@inheritDoc} */ 128 override fun isAboveAppWindowVisible( 129 componentMatcher: IComponentMatcher 130 ): WindowManagerTraceSubject = isAboveAppWindowVisible(componentMatcher, isOptional = false) 131 132 /** See [isAboveAppWindowVisible] */ 133 fun isAboveAppWindowVisible( 134 componentMatcher: IComponentMatcher, 135 isOptional: Boolean 136 ): WindowManagerTraceSubject = apply { 137 addAssertion( 138 "isAboveAppWindowVisible(${componentMatcher.toWindowIdentifier()})", 139 isOptional 140 ) { 141 it.isAboveAppWindowVisible(componentMatcher) 142 } 143 } 144 145 /** {@inheritDoc} */ 146 override fun isAboveAppWindowInvisible( 147 componentMatcher: IComponentMatcher 148 ): WindowManagerTraceSubject = isAboveAppWindowInvisible(componentMatcher, isOptional = false) 149 150 /** See [isAboveAppWindowInvisible] */ 151 fun isAboveAppWindowInvisible( 152 componentMatcher: IComponentMatcher, 153 isOptional: Boolean 154 ): WindowManagerTraceSubject = apply { 155 addAssertion( 156 "isAboveAppWindowInvisible(${componentMatcher.toWindowIdentifier()})", 157 isOptional 158 ) { 159 it.isAboveAppWindowInvisible(componentMatcher) 160 } 161 } 162 163 /** {@inheritDoc} */ 164 override fun isBelowAppWindowVisible( 165 componentMatcher: IComponentMatcher 166 ): WindowManagerTraceSubject = isBelowAppWindowVisible(componentMatcher, isOptional = false) 167 168 /** See [isBelowAppWindowVisible] */ 169 fun isBelowAppWindowVisible( 170 componentMatcher: IComponentMatcher, 171 isOptional: Boolean 172 ): WindowManagerTraceSubject = apply { 173 addAssertion( 174 "isBelowAppWindowVisible(${componentMatcher.toWindowIdentifier()})", 175 isOptional 176 ) { 177 it.isBelowAppWindowVisible(componentMatcher) 178 } 179 } 180 181 /** {@inheritDoc} */ 182 override fun isBelowAppWindowInvisible( 183 componentMatcher: IComponentMatcher 184 ): WindowManagerTraceSubject = isBelowAppWindowInvisible(componentMatcher, isOptional = false) 185 186 /** See [isBelowAppWindowInvisible] */ 187 fun isBelowAppWindowInvisible( 188 componentMatcher: IComponentMatcher, 189 isOptional: Boolean 190 ): WindowManagerTraceSubject = apply { 191 addAssertion( 192 "isBelowAppWindowInvisible(${componentMatcher.toWindowIdentifier()})", 193 isOptional 194 ) { 195 it.isBelowAppWindowInvisible(componentMatcher) 196 } 197 } 198 199 /** {@inheritDoc} */ 200 override fun isNonAppWindowVisible( 201 componentMatcher: IComponentMatcher 202 ): WindowManagerTraceSubject = isNonAppWindowVisible(componentMatcher, isOptional = false) 203 204 /** See [isNonAppWindowVisible] */ 205 fun isNonAppWindowVisible( 206 componentMatcher: IComponentMatcher, 207 isOptional: Boolean 208 ): WindowManagerTraceSubject = apply { 209 addAssertion( 210 "isNonAppWindowVisible(${componentMatcher.toWindowIdentifier()})", 211 isOptional 212 ) { 213 it.isNonAppWindowVisible(componentMatcher) 214 } 215 } 216 217 /** {@inheritDoc} */ 218 override fun isNonAppWindowInvisible( 219 componentMatcher: IComponentMatcher 220 ): WindowManagerTraceSubject = isNonAppWindowInvisible(componentMatcher, isOptional = false) 221 222 /** See [isNonAppWindowInvisible] */ 223 fun isNonAppWindowInvisible( 224 componentMatcher: IComponentMatcher, 225 isOptional: Boolean 226 ): WindowManagerTraceSubject = apply { 227 addAssertion( 228 "isNonAppWindowInvisible(${componentMatcher.toWindowIdentifier()})", 229 isOptional 230 ) { 231 it.isNonAppWindowInvisible(componentMatcher) 232 } 233 } 234 235 /** {@inheritDoc} */ 236 override fun isAppWindowOnTop(componentMatcher: IComponentMatcher): WindowManagerTraceSubject = 237 isAppWindowOnTop(componentMatcher, isOptional = false) 238 239 /** See [isAppWindowOnTop] */ 240 fun isAppWindowOnTop( 241 componentMatcher: IComponentMatcher, 242 isOptional: Boolean 243 ): WindowManagerTraceSubject = apply { 244 addAssertion("isAppWindowOnTop(${componentMatcher.toWindowIdentifier()})", isOptional) { 245 it.isAppWindowOnTop(componentMatcher) 246 } 247 } 248 249 /** {@inheritDoc} */ 250 override fun isAppWindowNotOnTop( 251 componentMatcher: IComponentMatcher 252 ): WindowManagerTraceSubject = isAppWindowNotOnTop(componentMatcher, isOptional = false) 253 254 /** See [isAppWindowNotOnTop] */ 255 fun isAppWindowNotOnTop( 256 componentMatcher: IComponentMatcher, 257 isOptional: Boolean 258 ): WindowManagerTraceSubject = apply { 259 addAssertion("appWindowNotOnTop(${componentMatcher.toWindowIdentifier()})", isOptional) { 260 it.isAppWindowNotOnTop(componentMatcher) 261 } 262 } 263 264 /** {@inheritDoc} */ 265 override fun isAppWindowVisible( 266 componentMatcher: IComponentMatcher 267 ): WindowManagerTraceSubject = isAppWindowVisible(componentMatcher, isOptional = false) 268 269 /** See [isAppWindowVisible] */ 270 fun isAppWindowVisible( 271 componentMatcher: IComponentMatcher, 272 isOptional: Boolean 273 ): WindowManagerTraceSubject = apply { 274 addAssertion("isAppWindowVisible(${componentMatcher.toWindowIdentifier()})", isOptional) { 275 it.isAppWindowVisible(componentMatcher) 276 } 277 } 278 279 /** {@inheritDoc} */ 280 override fun hasNoVisibleAppWindow(): WindowManagerTraceSubject = 281 hasNoVisibleAppWindow(isOptional = false) 282 283 /** See [hasNoVisibleAppWindow] */ 284 fun hasNoVisibleAppWindow(isOptional: Boolean): WindowManagerTraceSubject = apply { 285 addAssertion("hasNoVisibleAppWindow()", isOptional) { it.hasNoVisibleAppWindow() } 286 } 287 288 /** {@inheritDoc} */ 289 override fun isKeyguardShowing(): WindowManagerTraceSubject = 290 isKeyguardShowing(isOptional = false) 291 292 /** See [isKeyguardShowing] */ 293 fun isKeyguardShowing(isOptional: Boolean): WindowManagerTraceSubject = apply { 294 addAssertion("isKeyguardShowing()", isOptional) { it.isKeyguardShowing() } 295 } 296 297 /** {@inheritDoc} */ 298 override fun isAppSnapshotStartingWindowVisibleFor( 299 componentMatcher: IComponentMatcher 300 ): WindowManagerTraceSubject = 301 isAppSnapshotStartingWindowVisibleFor(componentMatcher, isOptional = false) 302 303 /** See [isAppSnapshotStartingWindowVisibleFor] */ 304 fun isAppSnapshotStartingWindowVisibleFor( 305 componentMatcher: IComponentMatcher, 306 isOptional: Boolean 307 ): WindowManagerTraceSubject = apply { 308 addAssertion( 309 "isAppSnapshotStartingWindowVisibleFor(${componentMatcher.toWindowIdentifier()})", 310 isOptional 311 ) { 312 it.isAppSnapshotStartingWindowVisibleFor(componentMatcher) 313 } 314 } 315 316 /** {@inheritDoc} */ 317 override fun isAppWindowInvisible( 318 componentMatcher: IComponentMatcher 319 ): WindowManagerTraceSubject = isAppWindowInvisible(componentMatcher, isOptional = false) 320 321 /** See [isAppWindowInvisible] */ 322 fun isAppWindowInvisible( 323 componentMatcher: IComponentMatcher, 324 isOptional: Boolean 325 ): WindowManagerTraceSubject = apply { 326 addAssertion("isAppWindowInvisible(${componentMatcher.toWindowIdentifier()})", isOptional) { 327 it.isAppWindowInvisible(componentMatcher) 328 } 329 } 330 331 /** {@inheritDoc} */ 332 override fun doNotOverlap( 333 vararg componentMatcher: IComponentMatcher 334 ): WindowManagerTraceSubject = apply { 335 val repr = componentMatcher.joinToString(", ") { it.toWindowIdentifier() } 336 addAssertion("noWindowsOverlap($repr)") { it.doNotOverlap(*componentMatcher) } 337 } 338 339 /** {@inheritDoc} */ 340 override fun isAboveWindow( 341 aboveWindowComponentMatcher: IComponentMatcher, 342 belowWindowComponentMatcher: IComponentMatcher 343 ): WindowManagerTraceSubject = apply { 344 val aboveWindowTitle = aboveWindowComponentMatcher.toWindowIdentifier() 345 val belowWindowTitle = belowWindowComponentMatcher.toWindowIdentifier() 346 addAssertion("$aboveWindowTitle is above $belowWindowTitle") { 347 it.isAboveWindow(aboveWindowComponentMatcher, belowWindowComponentMatcher) 348 } 349 } 350 351 /** See [isAppWindowInvisible] */ 352 override fun visibleRegion(componentMatcher: IComponentMatcher?): RegionTraceSubject { 353 val regionTrace = 354 RegionTrace( 355 componentMatcher, 356 subjects.map { it.visibleRegion(componentMatcher).regionEntry } 357 ) 358 359 return RegionTraceSubject(regionTrace, reader) 360 } 361 362 /** {@inheritDoc} */ 363 override fun contains(componentMatcher: IComponentMatcher): WindowManagerTraceSubject = 364 contains(componentMatcher, isOptional = false) 365 366 /** See [contains] */ 367 fun contains( 368 componentMatcher: IComponentMatcher, 369 isOptional: Boolean 370 ): WindowManagerTraceSubject = apply { 371 addAssertion("contains(${componentMatcher.toWindowIdentifier()})", isOptional) { 372 it.contains(componentMatcher) 373 } 374 } 375 376 /** {@inheritDoc} */ 377 override fun containsAboveAppWindow( 378 componentMatcher: IComponentMatcher 379 ): WindowManagerTraceSubject = containsAboveAppWindow(componentMatcher, isOptional = false) 380 381 /** See [containsAboveAppWindow] */ 382 fun containsAboveAppWindow( 383 componentMatcher: IComponentMatcher, 384 isOptional: Boolean 385 ): WindowManagerTraceSubject = apply { 386 addAssertion( 387 "containsAboveAppWindow(${componentMatcher.toWindowIdentifier()})", 388 isOptional 389 ) { 390 it.containsAboveAppWindow(componentMatcher) 391 } 392 } 393 394 /** {@inheritDoc} */ 395 override fun containsAppWindow(componentMatcher: IComponentMatcher): WindowManagerTraceSubject = 396 containsAppWindow(componentMatcher, isOptional = false) 397 398 /** See [containsAppWindow] */ 399 fun containsAppWindow( 400 componentMatcher: IComponentMatcher, 401 isOptional: Boolean 402 ): WindowManagerTraceSubject = apply { 403 addAssertion("containsAppWindow(${componentMatcher.toWindowIdentifier()})", isOptional) { 404 it.containsAboveAppWindow(componentMatcher) 405 } 406 } 407 408 /** {@inheritDoc} */ 409 override fun containsBelowAppWindow( 410 componentMatcher: IComponentMatcher 411 ): WindowManagerTraceSubject = containsBelowAppWindow(componentMatcher, isOptional = false) 412 413 /** See [containsBelowAppWindow] */ 414 fun containsBelowAppWindow( 415 componentMatcher: IComponentMatcher, 416 isOptional: Boolean 417 ): WindowManagerTraceSubject = apply { 418 addAssertion( 419 "containsBelowAppWindows(${componentMatcher.toWindowIdentifier()})", 420 isOptional 421 ) { 422 it.containsBelowAppWindow(componentMatcher) 423 } 424 } 425 426 /** {@inheritDoc} */ 427 override fun containsNonAppWindow( 428 componentMatcher: IComponentMatcher 429 ): WindowManagerTraceSubject = containsNonAppWindow(componentMatcher, isOptional = false) 430 431 /** See [containsNonAppWindow] */ 432 fun containsNonAppWindow( 433 componentMatcher: IComponentMatcher, 434 isOptional: Boolean 435 ): WindowManagerTraceSubject = apply { 436 addAssertion("containsNonAppWindow(${componentMatcher.toWindowIdentifier()})", isOptional) { 437 it.containsNonAppWindow(componentMatcher) 438 } 439 } 440 441 /** {@inheritDoc} */ 442 override fun isHomeActivityInvisible(): WindowManagerTraceSubject = 443 isHomeActivityInvisible(isOptional = false) 444 445 /** See [isHomeActivityInvisible] */ 446 fun isHomeActivityInvisible(isOptional: Boolean): WindowManagerTraceSubject = apply { 447 addAssertion("isHomeActivityInvisible", isOptional) { it.isHomeActivityInvisible() } 448 } 449 450 /** {@inheritDoc} */ 451 override fun isHomeActivityVisible(): WindowManagerTraceSubject = 452 isHomeActivityVisible(isOptional = false) 453 454 /** See [isHomeActivityVisible] */ 455 fun isHomeActivityVisible(isOptional: Boolean): WindowManagerTraceSubject = apply { 456 addAssertion("isHomeActivityVisible", isOptional) { it.isHomeActivityVisible() } 457 } 458 459 /** {@inheritDoc} */ 460 override fun hasRotation(rotation: Rotation, displayId: Int): WindowManagerTraceSubject = 461 hasRotation(rotation, displayId, isOptional = false) 462 463 /** See [hasRotation] */ 464 fun hasRotation( 465 rotation: Rotation, 466 displayId: Int, 467 isOptional: Boolean 468 ): WindowManagerTraceSubject = apply { 469 addAssertion("hasRotation($rotation, display=$displayId)", isOptional) { 470 it.hasRotation(rotation, displayId) 471 } 472 } 473 474 /** {@inheritDoc} */ 475 override fun isNotPinned(componentMatcher: IComponentMatcher): WindowManagerTraceSubject = 476 isNotPinned(componentMatcher, isOptional = false) 477 478 /** See [isNotPinned] */ 479 fun isNotPinned( 480 componentMatcher: IComponentMatcher, 481 isOptional: Boolean 482 ): WindowManagerTraceSubject = apply { 483 addAssertion("isNotPinned(${componentMatcher.toWindowIdentifier()})", isOptional) { 484 it.isNotPinned(componentMatcher) 485 } 486 } 487 488 /** {@inheritDoc} */ 489 override fun isFocusedApp(app: String): WindowManagerTraceSubject = 490 isFocusedApp(app, isOptional = false) 491 492 /** See [isFocusedApp] */ 493 fun isFocusedApp(app: String, isOptional: Boolean): WindowManagerTraceSubject = apply { 494 addAssertion("isFocusedApp($app)", isOptional) { it.isFocusedApp(app) } 495 } 496 497 /** {@inheritDoc} */ 498 override fun isNotFocusedApp(app: String): WindowManagerTraceSubject = 499 isNotFocusedApp(app, isOptional = false) 500 501 /** See [isNotFocusedApp] */ 502 fun isNotFocusedApp(app: String, isOptional: Boolean): WindowManagerTraceSubject = apply { 503 addAssertion("isNotFocusedApp($app)", isOptional) { it.isNotFocusedApp(app) } 504 } 505 506 /** {@inheritDoc} */ 507 override fun isPinned(componentMatcher: IComponentMatcher): WindowManagerTraceSubject = 508 isPinned(componentMatcher, isOptional = false) 509 510 /** See [isPinned] */ 511 fun isPinned( 512 componentMatcher: IComponentMatcher, 513 isOptional: Boolean 514 ): WindowManagerTraceSubject = apply { 515 addAssertion("isPinned(${componentMatcher.toWindowIdentifier()})", isOptional) { 516 it.isPinned(componentMatcher) 517 } 518 } 519 520 /** {@inheritDoc} */ 521 override fun isRecentsActivityInvisible(): WindowManagerTraceSubject = 522 isRecentsActivityInvisible(isOptional = false) 523 524 /** See [isRecentsActivityInvisible] */ 525 fun isRecentsActivityInvisible(isOptional: Boolean): WindowManagerTraceSubject = apply { 526 addAssertion("isRecentsActivityInvisible", isOptional) { it.isRecentsActivityInvisible() } 527 } 528 529 /** {@inheritDoc} */ 530 override fun isRecentsActivityVisible(): WindowManagerTraceSubject = 531 isRecentsActivityVisible(isOptional = false) 532 533 /** See [isRecentsActivityVisible] */ 534 fun isRecentsActivityVisible(isOptional: Boolean): WindowManagerTraceSubject = apply { 535 addAssertion("isRecentsActivityVisible", isOptional) { it.isRecentsActivityVisible() } 536 } 537 538 override fun isValid(): WindowManagerTraceSubject = apply { 539 addAssertion("isValid") { it.isValid() } 540 } 541 542 /** {@inheritDoc} */ 543 override fun notContainsAppWindow( 544 componentMatcher: IComponentMatcher 545 ): WindowManagerTraceSubject = notContainsAppWindow(componentMatcher, isOptional = false) 546 547 /** See [notContainsAppWindow] */ 548 fun notContainsAppWindow( 549 componentMatcher: IComponentMatcher, 550 isOptional: Boolean 551 ): WindowManagerTraceSubject = apply { 552 addAssertion("notContainsAppWindow(${componentMatcher.toWindowIdentifier()})", isOptional) { 553 it.notContainsAppWindow(componentMatcher) 554 } 555 } 556 557 /** {@inheritDoc} */ 558 override fun containsAtLeastOneDisplay(): WindowManagerTraceSubject = apply { 559 addAssertion("containAtLeastOneDisplay", isOptional = false) { 560 it.containsAtLeastOneDisplay() 561 } 562 } 563 564 /** Checks that all visible layers are shown for more than one consecutive entry */ 565 fun visibleWindowsShownMoreThanOneConsecutiveEntry( 566 ignoreWindows: List<ComponentNameMatcher> = 567 listOf( 568 ComponentNameMatcher.SPLASH_SCREEN, 569 ComponentNameMatcher.SNAPSHOT, 570 ComponentNameMatcher.SECONDARY_HOME_HANDLE, 571 ComponentNameMatcher.EDGE_BACK_GESTURE_HANDLER, 572 ) 573 ): WindowManagerTraceSubject = apply { 574 visibleEntriesShownMoreThanOneConsecutiveTime { subject -> 575 subject.wmState.windowStates 576 .filter { it.isVisible } 577 .filter { window -> 578 ignoreWindows.none { matcher -> matcher.windowMatchesAnyOf(window) } 579 } 580 .map { it.name } 581 .toSet() 582 } 583 } 584 585 /** Executes a custom [assertion] on the current subject */ 586 operator fun invoke( 587 name: String, 588 isOptional: Boolean = false, 589 assertion: (WindowManagerStateSubject) -> Unit 590 ): WindowManagerTraceSubject = apply { addAssertion(name, isOptional, assertion) } 591 592 /** Run the assertions for all trace entries within the specified time range */ 593 fun forElapsedTimeRange(startTime: Long, endTime: Long) { 594 val subjectsInRange = 595 subjects.filter { it.wmState.timestamp.elapsedNanos in startTime..endTime } 596 assertionsChecker.test(subjectsInRange) 597 } 598 599 /** 600 * User-defined entry point for the trace entry with [timestamp] 601 * 602 * @param timestamp of the entry 603 */ 604 fun getEntryByElapsedTimestamp(timestamp: Long): WindowManagerStateSubject = 605 subjects.first { it.wmState.timestamp.elapsedNanos == timestamp } 606 } 607