blob: d50a7ee4cf84d9368fa13606f53e85e5caea2b18 [file] [log] [blame]
/*
* Copyright (C) 2022 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 com.android.server.wm.parser.windowmanager
import android.annotation.SuppressLint
import androidx.test.filters.FlakyTest
import androidx.test.platform.app.InstrumentationRegistry
import com.android.server.wm.InitRule
import com.android.server.wm.flicker.readWmTraceFromDumpFile
import com.android.server.wm.flicker.readWmTraceFromFile
import com.android.server.wm.traces.common.ActiveBuffer
import com.android.server.wm.traces.common.Cache
import com.android.server.wm.traces.common.Color
import com.android.server.wm.traces.common.CrossPlatform
import com.android.server.wm.traces.common.DeviceStateDump
import com.android.server.wm.traces.common.Matrix33
import com.android.server.wm.traces.common.Rect
import com.android.server.wm.traces.common.RectF
import com.android.server.wm.traces.common.component.IComponentName
import com.android.server.wm.traces.common.component.matchers.ComponentNameMatcher
import com.android.server.wm.traces.common.layers.HwcCompositionType
import com.android.server.wm.traces.common.layers.Layer
import com.android.server.wm.traces.common.layers.LayerTraceEntryBuilder
import com.android.server.wm.traces.common.layers.Transform
import com.android.server.wm.traces.common.region.Region
import com.android.server.wm.traces.common.service.PlatformConsts
import com.android.server.wm.traces.common.subjects.wm.WindowManagerStateSubject
import com.android.server.wm.traces.common.windowmanager.WindowManagerState
import com.android.server.wm.traces.common.windowmanager.WindowManagerTrace
import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
import com.google.common.truth.Truth
import org.junit.Before
import org.junit.ClassRule
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runners.MethodSorters
/**
* Contains [WindowManagerStateHelper] tests. To run this test: `atest
* FlickerLibTest:WindowManagerTraceHelperTest`
*/
@SuppressLint("VisibleForTests")
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
class WindowManagerStateHelperTest {
class TestWindowManagerStateHelper(
_wmState: WindowManagerState,
/** Predicate to supply a new UI information */
deviceDumpSupplier: () -> DeviceStateDump,
numRetries: Int = 5,
retryIntervalMs: Long = 500L
) :
WindowManagerStateHelper(
InstrumentationRegistry.getInstrumentation(),
clearCacheAfterParsing = false,
deviceDumpSupplier,
numRetries,
retryIntervalMs
) {
var wmState: WindowManagerState = _wmState
private set
override fun updateCurrState(value: DeviceStateDump) {
wmState = value.wmState
}
}
private val chromeComponent =
ComponentNameMatcher.unflattenFromString(
"com.android.chrome/org.chromium.chrome.browser" + ".firstrun.FirstRunActivity"
)
private val simpleAppComponentName =
ComponentNameMatcher.unflattenFromString(
"com.android.server.wm.flicker.testapp/.SimpleActivity"
)
@Before
fun before() {
Cache.clear()
}
private fun createImaginaryLayer(name: String, index: Int, id: Int, parentId: Int): Layer {
val transform = Transform.from(0, Matrix33.EMPTY)
val rect =
RectF.from(
left = index.toFloat(),
top = index.toFloat(),
right = index.toFloat() + 1,
bottom = index.toFloat() + 1
)
return Layer.from(
name,
id,
parentId,
z = 0,
visibleRegion = Region.from(rect.toRect()),
activeBuffer = ActiveBuffer.from(1, 1, 1, 1),
flags = 0,
bounds = rect,
color = Color.DEFAULT,
isOpaque = true,
shadowRadius = 0f,
cornerRadius = 0f,
type = "",
screenBounds = rect,
transform = transform,
sourceBounds = rect,
currFrame = 0,
effectiveScalingMode = 0,
bufferTransform = transform,
hwcCompositionType = HwcCompositionType.INVALID,
hwcCrop = RectF.EMPTY,
hwcFrame = Rect.EMPTY,
crop = rect.toRect(),
backgroundBlurRadius = 0,
isRelativeOf = false,
zOrderRelativeOfId = -1,
stackId = 0,
requestedTransform = transform,
requestedColor = Color.DEFAULT,
cornerRadiusCrop = RectF.EMPTY,
inputTransform = transform,
inputRegion = Region.from(rect.toRect()),
excludesCompositionState = false
)
}
private fun createImaginaryVisibleLayers(names: List<IComponentName>): Array<Layer> {
val root = createImaginaryLayer("root", -1, id = "root".hashCode(), parentId = -1)
val layers = mutableListOf(root)
names.forEachIndexed { index, name ->
layers.add(
createImaginaryLayer(
name.toLayerName(),
index,
id = name.hashCode(),
parentId = root.id
)
)
}
return layers.toTypedArray()
}
/**
* Creates a device state dump provider based on the WM trace
*
* Alongside the SF trac,e this function creates an imaginary SF trace with visible Status and
* NavBar, as well as all visible non-system windows (those with name containing /)
*/
private fun WindowManagerTrace.asSupplier(startingTimestamp: Long = 0): () -> DeviceStateDump {
val iterator = this.dropWhile { it.timestamp.elapsedNanos < startingTimestamp }.iterator()
return {
if (iterator.hasNext()) {
val wmState = iterator.next()
val layerList: MutableList<IComponentName> =
mutableListOf(ComponentNameMatcher.STATUS_BAR, ComponentNameMatcher.NAV_BAR)
if (wmState.isWindowSurfaceShown(ComponentNameMatcher.SPLASH_SCREEN)) {
layerList.add(ComponentNameMatcher.SPLASH_SCREEN)
}
if (wmState.isWindowSurfaceShown(ComponentNameMatcher.SNAPSHOT)) {
layerList.add(ComponentNameMatcher.SNAPSHOT)
}
layerList.addAll(
wmState.visibleWindows
.filter { it.name.contains("/") }
.map { ComponentNameMatcher.unflattenFromString(it.name) }
)
if (wmState.inputMethodWindowState?.isSurfaceShown == true) {
layerList.add(ComponentNameMatcher.IME)
}
val layerTraceEntry =
LayerTraceEntryBuilder()
.setElapsedTimestamp("0")
.setDisplays(emptyArray())
.setLayers(createImaginaryVisibleLayers(layerList))
.setVSyncId("-1")
.build()
DeviceStateDump(wmState, layerTraceEntry)
} else {
error("Reached the end of the trace")
}
}
}
@Test
fun canWaitForIme() {
val trace = readWmTraceFromFile("wm_trace_ime.pb", legacyTrace = true)
val supplier = trace.asSupplier()
val helper =
TestWindowManagerStateHelper(
trace.first(),
supplier,
numRetries = trace.entries.size,
retryIntervalMs = 1
)
try {
WindowManagerStateSubject(helper.wmState)
.isNonAppWindowVisible(ComponentNameMatcher.IME)
error("IME state should not be available")
} catch (e: AssertionError) {
helper.StateSyncBuilder().withImeShown().waitFor()
WindowManagerStateSubject(helper.wmState)
.isNonAppWindowVisible(ComponentNameMatcher.IME)
}
}
@Test
fun canFailImeNotShown() {
val trace = readWmTraceFromFile("wm_trace_ime.pb", legacyTrace = true)
val supplier = trace.asSupplier()
val helper =
TestWindowManagerStateHelper(
trace.first(),
supplier,
numRetries = trace.entries.size,
retryIntervalMs = 1
)
try {
WindowManagerStateSubject(helper.wmState)
.isNonAppWindowVisible(ComponentNameMatcher.IME)
error("IME state should not be available")
} catch (e: AssertionError) {
helper.StateSyncBuilder().withImeShown().waitFor()
WindowManagerStateSubject(helper.wmState)
.isNonAppWindowVisible(ComponentNameMatcher.IME)
}
}
@Test
fun canWaitForWindow() {
val trace = readWmTraceFromFile("wm_trace_open_app_cold.pb", legacyTrace = true)
val supplier = trace.asSupplier()
val helper =
TestWindowManagerStateHelper(
trace.first(),
supplier,
numRetries = trace.entries.size,
retryIntervalMs = 1
)
try {
WindowManagerStateSubject(helper.wmState).containsAppWindow(simpleAppComponentName)
error("Chrome window should not exist in the start of the trace")
} catch (e: AssertionError) {
helper.StateSyncBuilder().withWindowSurfaceAppeared(simpleAppComponentName).waitFor()
WindowManagerStateSubject(helper.wmState).isAppWindowVisible(simpleAppComponentName)
}
}
@Test
fun canFailWindowNotShown() {
val trace = readWmTraceFromFile("wm_trace_open_app_cold.pb", legacyTrace = true)
val supplier = trace.asSupplier()
val helper =
TestWindowManagerStateHelper(
trace.first(),
supplier,
numRetries = 3,
retryIntervalMs = 1
)
try {
WindowManagerStateSubject(helper.wmState).containsAppWindow(simpleAppComponentName)
error("SimpleActivity window should not exist in the start of the trace")
} catch (e: AssertionError) {
// nothing to do
}
try {
helper.StateSyncBuilder().withWindowSurfaceAppeared(simpleAppComponentName).waitFor()
} catch (e: IllegalArgumentException) {
WindowManagerStateSubject(helper.wmState).notContains(simpleAppComponentName)
}
}
@Test
fun canDetectHomeActivityVisibility() {
val trace = readWmTraceFromFile("wm_trace_open_and_close_chrome.pb", legacyTrace = true)
val supplier = trace.asSupplier()
val helper =
TestWindowManagerStateHelper(
trace.first(),
supplier,
numRetries = trace.entries.size,
retryIntervalMs = 1
)
WindowManagerStateSubject(helper.wmState).isHomeActivityVisible()
helper.StateSyncBuilder().withWindowSurfaceAppeared(chromeComponent).waitFor()
WindowManagerStateSubject(helper.wmState).isHomeActivityInvisible()
helper.StateSyncBuilder().withHomeActivityVisible().waitFor()
WindowManagerStateSubject(helper.wmState).isHomeActivityVisible()
}
@Test
fun canWaitActivityRemoved() {
val trace = readWmTraceFromFile("wm_trace_open_and_close_chrome.pb", legacyTrace = true)
val supplier = trace.asSupplier()
val helper =
TestWindowManagerStateHelper(
trace.first(),
supplier,
numRetries = trace.entries.size,
retryIntervalMs = 1
)
WindowManagerStateSubject(helper.wmState)
.isHomeActivityVisible()
.notContains(chromeComponent)
helper.StateSyncBuilder().withWindowSurfaceAppeared(chromeComponent).waitFor()
WindowManagerStateSubject(helper.wmState).isAppWindowVisible(chromeComponent)
helper.StateSyncBuilder().withActivityRemoved(chromeComponent).waitFor()
WindowManagerStateSubject(helper.wmState)
.notContains(chromeComponent)
.isHomeActivityVisible()
}
@Test
fun canWaitAppStateIdle() {
val trace = readWmTraceFromFile("wm_trace_open_and_close_chrome.pb", legacyTrace = true)
val initialTimestamp = 69443918698679
val supplier = trace.asSupplier(startingTimestamp = initialTimestamp)
val initialEntry =
trace.getEntryExactlyAt(CrossPlatform.timestamp.from(elapsedNanos = initialTimestamp))
val helper =
TestWindowManagerStateHelper(
initialEntry,
supplier,
numRetries = trace.entries.size,
retryIntervalMs = 1
)
try {
WindowManagerStateSubject(helper.wmState).isValid()
error("Initial state in the trace should not be valid")
} catch (e: AssertionError) {
Truth.assertWithMessage("App transition never became idle")
.that(helper.StateSyncBuilder().withAppTransitionIdle().waitFor())
.isTrue()
WindowManagerStateSubject(helper.wmState).isValid()
}
}
@Test
fun canWaitForRotation() {
val trace = readWmTraceFromFile("wm_trace_rotation.pb", legacyTrace = true)
val supplier = trace.asSupplier()
val helper =
TestWindowManagerStateHelper(
trace.first(),
supplier,
numRetries = trace.entries.size,
retryIntervalMs = 1
)
WindowManagerStateSubject(helper.wmState).hasRotation(PlatformConsts.Rotation.ROTATION_0)
helper.StateSyncBuilder().withRotation(PlatformConsts.Rotation.ROTATION_270).waitFor()
WindowManagerStateSubject(helper.wmState).hasRotation(PlatformConsts.Rotation.ROTATION_270)
helper.StateSyncBuilder().withRotation(PlatformConsts.Rotation.ROTATION_0).waitFor()
WindowManagerStateSubject(helper.wmState).hasRotation(PlatformConsts.Rotation.ROTATION_0)
}
@Test
fun canDetectResumedActivitiesInStacks() {
val trace = readWmTraceFromDumpFile("wm_trace_resumed_activities_in_stack.pb")
val entry = trace.first()
Truth.assertWithMessage("Trace should have a resumed activity in stacks")
.that(entry.resumedActivities)
.asList()
.hasSize(1)
}
@FlakyTest
@Test
fun canWaitForRecents() {
val trace = readWmTraceFromFile("wm_trace_open_recents.pb", legacyTrace = true)
val supplier = trace.asSupplier()
val helper =
TestWindowManagerStateHelper(
trace.first(),
supplier,
numRetries = trace.entries.size,
retryIntervalMs = 1
)
WindowManagerStateSubject(helper.wmState).isRecentsActivityInvisible()
helper.StateSyncBuilder().withRecentsActivityVisible().waitFor()
WindowManagerStateSubject(helper.wmState).isRecentsActivityVisible()
}
companion object {
@ClassRule @JvmField val initRule = InitRule()
}
}