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