blob: 0c29706a0a7edc145561acd6b6e13c6e0450c240 [file] [log] [blame]
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.tools.common.flicker.subject.wm
import android.tools.common.Rotation
import android.tools.common.datatypes.component.ComponentNameMatcher
import android.tools.common.datatypes.component.IComponentMatcher
import android.tools.common.flicker.assertions.Fact
import android.tools.common.flicker.subject.FlickerTraceSubject
import android.tools.common.flicker.subject.region.RegionTraceSubject
import android.tools.common.traces.region.RegionTrace
import android.tools.common.traces.wm.WindowManagerTrace
import android.tools.common.traces.wm.WindowState
/**
* Subject for [WindowManagerTrace] objects, used to make assertions over behaviors that occur
* throughout a whole trace.
*
* To make assertions over a trace it is recommended to create a subject using
* [WindowManagerTraceSubject](myTrace).
*
* Example:
* ```
* val trace = WindowManagerTraceParser().parse(myTraceFile)
* val subject = WindowManagerTraceSubject(trace)
* .contains("ValidWindow")
* .notContains("ImaginaryWindow")
* .showsAboveAppWindow("NavigationBar")
* .forAllEntries()
* ```
* Example2:
* ```
* val trace = WindowManagerTraceParser().parse(myTraceFile)
* val subject = WindowManagerTraceSubject(trace) {
* check(myCustomAssertion(this)) { "My assertion lazy message" }
* }
* ```
*/
class WindowManagerTraceSubject(
val trace: WindowManagerTrace,
override val parent: WindowManagerTraceSubject? = null,
private val facts: Collection<Fact> = emptyList()
) :
FlickerTraceSubject<WindowManagerStateSubject>(),
IWindowManagerSubject<WindowManagerTraceSubject, RegionTraceSubject> {
override val selfFacts by lazy {
val allFacts = super.selfFacts.toMutableList()
allFacts.addAll(facts)
allFacts
}
override val subjects by lazy {
trace.entries.map { WindowManagerStateSubject(it, this, this) }
}
/** {@inheritDoc} */
override fun then(): WindowManagerTraceSubject = apply { super.then() }
/** {@inheritDoc} */
override fun skipUntilFirstAssertion(): WindowManagerTraceSubject = apply {
super.skipUntilFirstAssertion()
}
/** {@inheritDoc} */
override fun isEmpty(): WindowManagerTraceSubject = apply {
check { "Trace is empty" }.that(trace.entries.isEmpty()).isEqual(true)
}
/** {@inheritDoc} */
override fun isNotEmpty(): WindowManagerTraceSubject = apply {
check { "Trace is not empty" }.that(trace.entries.isEmpty()).isEqual(false)
}
/**
* @return List of [WindowStateSubject]s matching [componentMatcher] in the order they
* ```
* appear on the trace
*
* @param componentMatcher
* ```
* Components to search
*/
fun windowStates(componentMatcher: IComponentMatcher): List<WindowStateSubject> = windowStates {
componentMatcher.windowMatchesAnyOf(it)
}
/**
* @return List of [WindowStateSubject]s matching [predicate] in the order they
* ```
* appear on the trace
*
* @param predicate
* ```
* To search
*/
fun windowStates(predicate: (WindowState) -> Boolean): List<WindowStateSubject> {
return subjects.mapNotNull { it.windowState { window -> predicate(window) } }
}
/** {@inheritDoc} */
override fun notContains(componentMatcher: IComponentMatcher): WindowManagerTraceSubject =
notContains(componentMatcher, isOptional = false)
/** See [notContains] */
fun notContains(
componentMatcher: IComponentMatcher,
isOptional: Boolean
): WindowManagerTraceSubject = apply {
addAssertion("notContains(${componentMatcher.toWindowIdentifier()})", isOptional) {
it.notContains(componentMatcher)
}
}
/** {@inheritDoc} */
override fun isAboveAppWindowVisible(
componentMatcher: IComponentMatcher
): WindowManagerTraceSubject = isAboveAppWindowVisible(componentMatcher, isOptional = false)
/** See [isAboveAppWindowVisible] */
fun isAboveAppWindowVisible(
componentMatcher: IComponentMatcher,
isOptional: Boolean
): WindowManagerTraceSubject = apply {
addAssertion(
"isAboveAppWindowVisible(${componentMatcher.toWindowIdentifier()})",
isOptional
) { it.isAboveAppWindowVisible(componentMatcher) }
}
/** {@inheritDoc} */
override fun isAboveAppWindowInvisible(
componentMatcher: IComponentMatcher
): WindowManagerTraceSubject = isAboveAppWindowInvisible(componentMatcher, isOptional = false)
/** See [isAboveAppWindowInvisible] */
fun isAboveAppWindowInvisible(
componentMatcher: IComponentMatcher,
isOptional: Boolean
): WindowManagerTraceSubject = apply {
addAssertion(
"isAboveAppWindowInvisible(${componentMatcher.toWindowIdentifier()})",
isOptional
) { it.isAboveAppWindowInvisible(componentMatcher) }
}
/** {@inheritDoc} */
override fun isBelowAppWindowVisible(
componentMatcher: IComponentMatcher
): WindowManagerTraceSubject = isBelowAppWindowVisible(componentMatcher, isOptional = false)
/** See [isBelowAppWindowVisible] */
fun isBelowAppWindowVisible(
componentMatcher: IComponentMatcher,
isOptional: Boolean
): WindowManagerTraceSubject = apply {
addAssertion(
"isBelowAppWindowVisible(${componentMatcher.toWindowIdentifier()})",
isOptional
) { it.isBelowAppWindowVisible(componentMatcher) }
}
/** {@inheritDoc} */
override fun isBelowAppWindowInvisible(
componentMatcher: IComponentMatcher
): WindowManagerTraceSubject = isBelowAppWindowInvisible(componentMatcher, isOptional = false)
/** See [isBelowAppWindowInvisible] */
fun isBelowAppWindowInvisible(
componentMatcher: IComponentMatcher,
isOptional: Boolean
): WindowManagerTraceSubject = apply {
addAssertion(
"isBelowAppWindowInvisible(${componentMatcher.toWindowIdentifier()})",
isOptional
) { it.isBelowAppWindowInvisible(componentMatcher) }
}
/** {@inheritDoc} */
override fun isNonAppWindowVisible(
componentMatcher: IComponentMatcher
): WindowManagerTraceSubject = isNonAppWindowVisible(componentMatcher, isOptional = false)
/** See [isNonAppWindowVisible] */
fun isNonAppWindowVisible(
componentMatcher: IComponentMatcher,
isOptional: Boolean
): WindowManagerTraceSubject = apply {
addAssertion(
"isNonAppWindowVisible(${componentMatcher.toWindowIdentifier()})",
isOptional
) { it.isNonAppWindowVisible(componentMatcher) }
}
/** {@inheritDoc} */
override fun isNonAppWindowInvisible(
componentMatcher: IComponentMatcher
): WindowManagerTraceSubject = isNonAppWindowInvisible(componentMatcher, isOptional = false)
/** See [isNonAppWindowInvisible] */
fun isNonAppWindowInvisible(
componentMatcher: IComponentMatcher,
isOptional: Boolean
): WindowManagerTraceSubject = apply {
addAssertion(
"isNonAppWindowInvisible(${componentMatcher.toWindowIdentifier()})",
isOptional
) { it.isNonAppWindowInvisible(componentMatcher) }
}
/** {@inheritDoc} */
override fun isAppWindowOnTop(componentMatcher: IComponentMatcher): WindowManagerTraceSubject =
isAppWindowOnTop(componentMatcher, isOptional = false)
/** See [isAppWindowOnTop] */
fun isAppWindowOnTop(
componentMatcher: IComponentMatcher,
isOptional: Boolean
): WindowManagerTraceSubject = apply {
addAssertion("isAppWindowOnTop(${componentMatcher.toWindowIdentifier()})", isOptional) {
it.isAppWindowOnTop(componentMatcher)
}
}
/** {@inheritDoc} */
override fun isAppWindowNotOnTop(
componentMatcher: IComponentMatcher
): WindowManagerTraceSubject = isAppWindowNotOnTop(componentMatcher, isOptional = false)
/** See [isAppWindowNotOnTop] */
fun isAppWindowNotOnTop(
componentMatcher: IComponentMatcher,
isOptional: Boolean
): WindowManagerTraceSubject = apply {
addAssertion("appWindowNotOnTop(${componentMatcher.toWindowIdentifier()})", isOptional) {
it.isAppWindowNotOnTop(componentMatcher)
}
}
/** {@inheritDoc} */
override fun isAppWindowVisible(
componentMatcher: IComponentMatcher
): WindowManagerTraceSubject = isAppWindowVisible(componentMatcher, isOptional = false)
/** See [isAppWindowVisible] */
fun isAppWindowVisible(
componentMatcher: IComponentMatcher,
isOptional: Boolean
): WindowManagerTraceSubject = apply {
addAssertion("isAppWindowVisible(${componentMatcher.toWindowIdentifier()})", isOptional) {
it.isAppWindowVisible(componentMatcher)
}
}
/** {@inheritDoc} */
override fun hasNoVisibleAppWindow(): WindowManagerTraceSubject =
hasNoVisibleAppWindow(isOptional = false)
/** See [hasNoVisibleAppWindow] */
fun hasNoVisibleAppWindow(isOptional: Boolean): WindowManagerTraceSubject = apply {
addAssertion("hasNoVisibleAppWindow()", isOptional) { it.hasNoVisibleAppWindow() }
}
/** {@inheritDoc} */
override fun isKeyguardShowing(): WindowManagerTraceSubject =
isKeyguardShowing(isOptional = false)
/** See [isKeyguardShowing] */
fun isKeyguardShowing(isOptional: Boolean): WindowManagerTraceSubject = apply {
addAssertion("isKeyguardShowing()", isOptional) { it.isKeyguardShowing() }
}
/** {@inheritDoc} */
override fun isAppSnapshotStartingWindowVisibleFor(
componentMatcher: IComponentMatcher
): WindowManagerTraceSubject =
isAppSnapshotStartingWindowVisibleFor(componentMatcher, isOptional = false)
/** See [isAppSnapshotStartingWindowVisibleFor] */
fun isAppSnapshotStartingWindowVisibleFor(
componentMatcher: IComponentMatcher,
isOptional: Boolean
): WindowManagerTraceSubject = apply {
addAssertion(
"isAppSnapshotStartingWindowVisibleFor(${componentMatcher.toWindowIdentifier()})",
isOptional
) { it.isAppSnapshotStartingWindowVisibleFor(componentMatcher) }
}
/** {@inheritDoc} */
override fun isAppWindowInvisible(
componentMatcher: IComponentMatcher
): WindowManagerTraceSubject = isAppWindowInvisible(componentMatcher, isOptional = false)
/** See [isAppWindowInvisible] */
fun isAppWindowInvisible(
componentMatcher: IComponentMatcher,
isOptional: Boolean
): WindowManagerTraceSubject = apply {
addAssertion("isAppWindowInvisible(${componentMatcher.toWindowIdentifier()})", isOptional) {
it.isAppWindowInvisible(componentMatcher)
}
}
/** {@inheritDoc} */
override fun doNotOverlap(
vararg componentMatcher: IComponentMatcher
): WindowManagerTraceSubject = apply {
val repr = componentMatcher.joinToString(", ") { it.toWindowIdentifier() }
addAssertion("noWindowsOverlap($repr)") { it.doNotOverlap(*componentMatcher) }
}
/** {@inheritDoc} */
override fun isAboveWindow(
aboveWindowComponentMatcher: IComponentMatcher,
belowWindowComponentMatcher: IComponentMatcher
): WindowManagerTraceSubject = apply {
val aboveWindowTitle = aboveWindowComponentMatcher.toWindowIdentifier()
val belowWindowTitle = belowWindowComponentMatcher.toWindowIdentifier()
addAssertion("$aboveWindowTitle is above $belowWindowTitle") {
it.isAboveWindow(aboveWindowComponentMatcher, belowWindowComponentMatcher)
}
}
/** See [isAppWindowInvisible] */
override fun visibleRegion(componentMatcher: IComponentMatcher?): RegionTraceSubject {
val regionTrace =
RegionTrace(
componentMatcher,
subjects.map { it.visibleRegion(componentMatcher).regionEntry }.toTypedArray()
)
return RegionTraceSubject(regionTrace, this)
}
/** {@inheritDoc} */
override fun contains(componentMatcher: IComponentMatcher): WindowManagerTraceSubject =
contains(componentMatcher, isOptional = false)
/** See [contains] */
fun contains(
componentMatcher: IComponentMatcher,
isOptional: Boolean
): WindowManagerTraceSubject = apply {
addAssertion("contains(${componentMatcher.toWindowIdentifier()})", isOptional) {
it.contains(componentMatcher)
}
}
/** {@inheritDoc} */
override fun containsAboveAppWindow(
componentMatcher: IComponentMatcher
): WindowManagerTraceSubject = containsAboveAppWindow(componentMatcher, isOptional = false)
/** See [containsAboveAppWindow] */
fun containsAboveAppWindow(
componentMatcher: IComponentMatcher,
isOptional: Boolean
): WindowManagerTraceSubject = apply {
addAssertion(
"containsAboveAppWindow(${componentMatcher.toWindowIdentifier()})",
isOptional
) { it.containsAboveAppWindow(componentMatcher) }
}
/** {@inheritDoc} */
override fun containsAppWindow(componentMatcher: IComponentMatcher): WindowManagerTraceSubject =
containsAppWindow(componentMatcher, isOptional = false)
/** See [containsAppWindow] */
fun containsAppWindow(
componentMatcher: IComponentMatcher,
isOptional: Boolean
): WindowManagerTraceSubject = apply {
addAssertion("containsAppWindow(${componentMatcher.toWindowIdentifier()})", isOptional) {
it.containsAboveAppWindow(componentMatcher)
}
}
/** {@inheritDoc} */
override fun containsBelowAppWindow(
componentMatcher: IComponentMatcher
): WindowManagerTraceSubject = containsBelowAppWindow(componentMatcher, isOptional = false)
/** See [containsBelowAppWindow] */
fun containsBelowAppWindow(
componentMatcher: IComponentMatcher,
isOptional: Boolean
): WindowManagerTraceSubject = apply {
addAssertion(
"containsBelowAppWindows(${componentMatcher.toWindowIdentifier()})",
isOptional
) { it.containsBelowAppWindow(componentMatcher) }
}
/** {@inheritDoc} */
override fun containsNonAppWindow(
componentMatcher: IComponentMatcher
): WindowManagerTraceSubject = containsNonAppWindow(componentMatcher, isOptional = false)
/** See [containsNonAppWindow] */
fun containsNonAppWindow(
componentMatcher: IComponentMatcher,
isOptional: Boolean
): WindowManagerTraceSubject = apply {
addAssertion("containsNonAppWindow(${componentMatcher.toWindowIdentifier()})", isOptional) {
it.containsNonAppWindow(componentMatcher)
}
}
/** {@inheritDoc} */
override fun isHomeActivityInvisible(): WindowManagerTraceSubject =
isHomeActivityInvisible(isOptional = false)
/** See [isHomeActivityInvisible] */
fun isHomeActivityInvisible(isOptional: Boolean): WindowManagerTraceSubject = apply {
addAssertion("isHomeActivityInvisible", isOptional) { it.isHomeActivityInvisible() }
}
/** {@inheritDoc} */
override fun isHomeActivityVisible(): WindowManagerTraceSubject =
isHomeActivityVisible(isOptional = false)
/** See [isHomeActivityVisible] */
fun isHomeActivityVisible(isOptional: Boolean): WindowManagerTraceSubject = apply {
addAssertion("isHomeActivityVisible", isOptional) { it.isHomeActivityVisible() }
}
/** {@inheritDoc} */
override fun hasRotation(rotation: Rotation, displayId: Int): WindowManagerTraceSubject =
hasRotation(rotation, displayId, isOptional = false)
/** See [hasRotation] */
fun hasRotation(
rotation: Rotation,
displayId: Int,
isOptional: Boolean
): WindowManagerTraceSubject = apply {
addAssertion("hasRotation($rotation, display=$displayId)", isOptional) {
it.hasRotation(rotation, displayId)
}
}
/** {@inheritDoc} */
override fun isNotPinned(componentMatcher: IComponentMatcher): WindowManagerTraceSubject =
isNotPinned(componentMatcher, isOptional = false)
/** See [isNotPinned] */
fun isNotPinned(
componentMatcher: IComponentMatcher,
isOptional: Boolean
): WindowManagerTraceSubject = apply {
addAssertion("isNotPinned(${componentMatcher.toWindowIdentifier()})", isOptional) {
it.isNotPinned(componentMatcher)
}
}
/** {@inheritDoc} */
override fun isFocusedApp(app: String): WindowManagerTraceSubject =
isFocusedApp(app, isOptional = false)
/** See [isFocusedApp] */
fun isFocusedApp(app: String, isOptional: Boolean): WindowManagerTraceSubject = apply {
addAssertion("isFocusedApp($app)", isOptional) { it.isFocusedApp(app) }
}
/** {@inheritDoc} */
override fun isNotFocusedApp(app: String): WindowManagerTraceSubject =
isNotFocusedApp(app, isOptional = false)
/** See [isNotFocusedApp] */
fun isNotFocusedApp(app: String, isOptional: Boolean): WindowManagerTraceSubject = apply {
addAssertion("isNotFocusedApp($app)", isOptional) { it.isNotFocusedApp(app) }
}
/** {@inheritDoc} */
override fun isPinned(componentMatcher: IComponentMatcher): WindowManagerTraceSubject =
isPinned(componentMatcher, isOptional = false)
/** See [isPinned] */
fun isPinned(
componentMatcher: IComponentMatcher,
isOptional: Boolean
): WindowManagerTraceSubject = apply {
addAssertion("isPinned(${componentMatcher.toWindowIdentifier()})", isOptional) {
it.isPinned(componentMatcher)
}
}
/** {@inheritDoc} */
override fun isRecentsActivityInvisible(): WindowManagerTraceSubject =
isRecentsActivityInvisible(isOptional = false)
/** See [isRecentsActivityInvisible] */
fun isRecentsActivityInvisible(isOptional: Boolean): WindowManagerTraceSubject = apply {
addAssertion("isRecentsActivityInvisible", isOptional) { it.isRecentsActivityInvisible() }
}
/** {@inheritDoc} */
override fun isRecentsActivityVisible(): WindowManagerTraceSubject =
isRecentsActivityVisible(isOptional = false)
/** See [isRecentsActivityVisible] */
fun isRecentsActivityVisible(isOptional: Boolean): WindowManagerTraceSubject = apply {
addAssertion("isRecentsActivityVisible", isOptional) { it.isRecentsActivityVisible() }
}
override fun isValid(): WindowManagerTraceSubject = apply {
addAssertion("isValid") { it.isValid() }
}
/** {@inheritDoc} */
override fun notContainsAppWindow(
componentMatcher: IComponentMatcher
): WindowManagerTraceSubject = notContainsAppWindow(componentMatcher, isOptional = false)
/** See [notContainsAppWindow] */
fun notContainsAppWindow(
componentMatcher: IComponentMatcher,
isOptional: Boolean
): WindowManagerTraceSubject = apply {
addAssertion("notContainsAppWindow(${componentMatcher.toWindowIdentifier()})", isOptional) {
it.notContainsAppWindow(componentMatcher)
}
}
/** Checks that all visible layers are shown for more than one consecutive entry */
fun visibleWindowsShownMoreThanOneConsecutiveEntry(
ignoreWindows: List<ComponentNameMatcher> =
listOf(ComponentNameMatcher.SPLASH_SCREEN, ComponentNameMatcher.SNAPSHOT)
): WindowManagerTraceSubject = apply {
visibleEntriesShownMoreThanOneConsecutiveTime { subject ->
subject.wmState.windowStates
.filter { it.isVisible }
.filter { ignoreWindows.none { windowName -> windowName.windowMatchesAnyOf(it) } }
.map { it.name }
.toSet()
}
}
/** Executes a custom [assertion] on the current subject */
operator fun invoke(
name: String,
isOptional: Boolean = false,
assertion: (WindowManagerStateSubject) -> Unit
): WindowManagerTraceSubject = apply { addAssertion(name, isOptional, assertion) }
/** Run the assertions for all trace entries within the specified time range */
fun forElapsedTimeRange(startTime: Long, endTime: Long) {
val subjectsInRange =
subjects.filter { it.wmState.timestamp.elapsedNanos in startTime..endTime }
assertionsChecker.test(subjectsInRange)
}
/**
* User-defined entry point for the trace entry with [timestamp]
*
* @param timestamp of the entry
*/
fun getEntryByElapsedTimestamp(timestamp: Long): WindowManagerStateSubject =
subjects.first { it.wmState.timestamp.elapsedNanos == timestamp }
}